Files
2026-04-09 15:10:07 +03:00

1174 lines
46 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Barotrauma.Extensions;
using File = Barotrauma.IO.File;
using FileStream = Barotrauma.IO.FileStream;
using Path = Barotrauma.IO.Path;
namespace Barotrauma
{
public static class XMLExtensions
{
private readonly static ImmutableDictionary<Type, Func<string, object, object>> Converters
= new Dictionary<Type, Func<string, object, object>>()
{
{ typeof(string), (str, defVal) => str },
{ typeof(int), (str, defVal) => int.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out int result) ? result : defVal },
{ typeof(uint), (str, defVal) => uint.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out uint result) ? result : defVal },
{ typeof(UInt64), (str, defVal) => UInt64.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out UInt64 result) ? result : defVal },
{ typeof(float), (str, defVal) => float.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out float result) ? result : defVal },
{ typeof(bool), (str, defVal) => bool.TryParse(str, out bool result) ? result : defVal },
{ typeof(Color), (str, defVal) => ParseColor(str) },
{ typeof(Vector2), (str, defVal) => ParseVector2(str) },
{ typeof(Vector3), (str, defVal) => ParseVector3(str) },
{ typeof(Vector4), (str, defVal) => ParseVector4(str) },
{ typeof(Rectangle), (str, defVal) => ParseRect(str, true) }
}.ToImmutableDictionary();
/// <summary>
/// Check if the given value equals to the default value of the property.
/// Takes into account that certain default values (e.g. Vectors and other values that aren't compile-time constants) are defined as strings.
/// </summary>
public static bool DefaultValueEquals(object defaultValue, object value)
{
//if the value is given as a string, check if there's a converter for the type of the default value and attempt converting
if (defaultValue != null && value is string valueAsString &&
Converters.TryGetKey(defaultValue.GetType(), out Type type))
{
return Equals(Converters[type].Invoke(valueAsString, defaultValue), defaultValue);
}
//other way around: default values is given as a string, check if there's a converter for the type of the value
else if (value != null && defaultValue is string defaultValueAsString &&
Converters.TryGetKey(value.GetType(), out Type type2))
{
return Equals(Converters[type2].Invoke(defaultValueAsString, value), value);
}
return Equals(value, defaultValue);
}
public static string ParseContentPathFromUri(this XObject element)
=> !string.IsNullOrWhiteSpace(element.BaseUri)
? System.IO.Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri.CleanUpPath())
: "";
public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
IgnoreWhitespace = true,
};
public static XmlReader CreateReader(System.IO.Stream stream, string baseUri = "")
=> XmlReader.Create(stream, ReaderSettings, baseUri);
public static XDocument TryLoadXml(System.IO.Stream stream)
{
XDocument doc;
try
{
using XmlReader reader = CreateReader(stream);
doc = XDocument.Load(reader);
}
catch (Exception e)
{
DebugConsole.ThrowError($"Couldn't load xml document from stream!", e);
return null;
}
if (doc?.Root == null)
{
DebugConsole.ThrowError("XML could not be loaded from stream: Document or the root element is invalid!");
return null;
}
return doc;
}
public static XDocument TryLoadXml(ContentPath path) => TryLoadXml(path.Value);
public static XDocument TryLoadXml(string filePath)
{
var doc = TryLoadXml(filePath, out var exception);
if (exception != null)
{
DebugConsole.ThrowError($"Couldn't load xml document \"{filePath}\"!", exception);
}
else if (doc is null)
{
DebugConsole.ThrowError($"File \"{filePath}\" could not be loaded: Document or the root element is invalid!");
}
return doc;
}
public static XDocument TryLoadXml(string filePath, out Exception exception)
{
exception = null;
XDocument doc;
try
{
ToolBox.IsProperFilenameCase(filePath);
using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read, catchUnauthorizedAccessExceptions: false);
using XmlReader reader = CreateReader(stream, Path.GetFullPath(filePath));
doc = XDocument.Load(reader, LoadOptions.SetBaseUri);
}
catch (Exception e)
{
exception = e;
return null;
}
if (doc?.Root == null)
{
return null;
}
return doc;
}
public static object GetAttributeObject(XAttribute attribute)
{
if (attribute == null) { return null; }
return ParseToObject(attribute.Value.ToString());
}
public static object ParseToObject(string value)
{
if (value.Contains(".") && Single.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float floatVal))
{
return floatVal;
}
if (Int32.TryParse(value, out int intVal))
{
return intVal;
}
string lowerTrimmedVal = value.ToLowerInvariant().Trim();
if (lowerTrimmedVal == "true")
{
return true;
}
if (lowerTrimmedVal == "false")
{
return false;
}
return value;
}
public static string GetAttributeString(this XElement element, string name, string defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
string str = GetAttributeString(attribute, defaultValue);
#if DEBUG
if (!str.IsNullOrEmpty() &&
(str.Contains("%ModDir", StringComparison.OrdinalIgnoreCase)
|| str.CleanUpPathCrossPlatform(correctFilenameCase: false).StartsWith("Content/", StringComparison.OrdinalIgnoreCase)))
{
DebugConsole.ThrowError($"Use {nameof(GetAttributeContentPath)} instead of {nameof(GetAttributeString)}\n{Environment.StackTrace.CleanupStackTrace()}");
if (Debugger.IsAttached) { Debugger.Break(); }
}
#endif
return str;
}
public static string GetAttributeStringUnrestricted(this XElement element, string name, string defaultValue)
{
#warning TODO: remove?
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
return GetAttributeString(attribute, defaultValue);
}
public static bool DoesAttributeReferenceFileNameAlone(this XElement element, string name)
{
string texName = element.GetAttributeStringUnrestricted(name, "");
return !texName.IsNullOrEmpty() & !texName.Contains("/") && !texName.Contains("%ModDir", StringComparison.OrdinalIgnoreCase);
}
public static ContentPath GetAttributeContentPath(this XElement element, string name, ContentPackage contentPackage)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return null; }
return ContentPath.FromRaw(contentPackage, GetAttributeString(attribute, null));
}
public static Identifier GetAttributeIdentifier(this XElement element, string name, string defaultValue)
{
return element.GetAttributeString(name, defaultValue).ToIdentifier();
}
public static Identifier GetAttributeIdentifier(this XElement element, string name, Identifier defaultValue)
{
return element.GetAttributeIdentifier(name, defaultValue.Value);
}
private static string GetAttributeString(XAttribute attribute, string defaultValue)
{
string value = attribute.Value;
return string.IsNullOrEmpty(value) ? defaultValue : value;
}
public static string[] GetAttributeStringArray(this XElement element, string name, string[] defaultValue, bool trim = true, bool convertToLowerInvariant = false)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
string stringValue = attribute.Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
string[] splitValue = stringValue.Split(',', '');
for (int i = 0; i < splitValue.Length; i++)
{
if (convertToLowerInvariant) { splitValue[i] = splitValue[i].ToLowerInvariant(); }
if (trim) { splitValue[i] = splitValue[i].Trim(); }
}
return splitValue;
}
public static Identifier[] GetAttributeIdentifierArray(this XElement element, Identifier[] defaultValue, params string[] matchingAttributeName)
{
if (element == null) { return defaultValue; }
foreach (string name in matchingAttributeName)
{
var value = element.GetAttributeIdentifierArray(name, defaultValue);
if (value != defaultValue) { return value; }
}
return defaultValue;
}
public static Identifier[] GetAttributeIdentifierArray(this XElement element, string name, Identifier[] defaultValue, bool trim = true)
{
return element.GetAttributeStringArray(name, null, trim: trim, convertToLowerInvariant: false)
?.ToIdentifiers()
?? defaultValue;
}
public static ImmutableHashSet<Identifier> GetAttributeIdentifierImmutableHashSet(this XElement element, string key, ImmutableHashSet<Identifier> defaultValue, bool trim = true)
{
return element.GetAttributeIdentifierArray(key, null, trim)?.ToImmutableHashSet()
?? defaultValue;
}
public static float? GetAttributeNullableFloat(this XElement element, string attributeName)
{
if (element.GetAttribute(attributeName) is XAttribute attribute)
{
// if there is an error in parsing, we will return the default value, which may cause edge case errors down the line
return GetAttributeFloat(attribute, 0.0f);
}
return null;
}
public static float GetAttributeFloat(this XElement element, float defaultValue, params string[] matchingAttributeName)
{
if (element == null) { return defaultValue; }
foreach (string name in matchingAttributeName)
{
var attribute = element.GetAttribute(name);
if (attribute == null) { continue; }
return GetAttributeFloat(attribute, defaultValue);
}
return defaultValue;
}
public static float GetAttributeFloat(this XElement element, string name, float defaultValue) => GetAttributeFloat(element?.GetAttribute(name), defaultValue);
public static float GetAttributeFloat(this XAttribute attribute, float defaultValue)
{
if (attribute == null) { return defaultValue; }
float val = defaultValue;
try
{
string strVal = attribute.Value;
if (strVal.LastOrDefault() == 'f')
{
strVal = strVal.Substring(0, strVal.Length - 1);
}
val = float.Parse(strVal, CultureInfo.InvariantCulture);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + attribute + "! ", e);
}
return val;
}
public static double GetAttributeDouble(this XElement element, string name, double defaultValue) => GetAttributeDouble(element?.GetAttribute(name), defaultValue);
public static double GetAttributeDouble(this XAttribute attribute, double defaultValue)
{
if (attribute == null) { return defaultValue; }
double val = defaultValue;
try
{
string strVal = attribute.Value;
if (strVal.LastOrDefault() == 'f')
{
strVal = strVal.Substring(0, strVal.Length - 1);
}
val = double.Parse(strVal, CultureInfo.InvariantCulture);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + attribute + "!", e);
}
return val;
}
public static float[] GetAttributeFloatArray(this XElement element, string name, float[] defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
string stringValue = attribute.Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
string[] splitValue = stringValue.Split(',');
float[] floatValue = new float[splitValue.Length];
for (int i = 0; i < splitValue.Length; i++)
{
try
{
string strVal = splitValue[i];
if (strVal.LastOrDefault() == 'f')
{
strVal = strVal.Substring(0, strVal.Length - 1);
}
floatValue[i] = float.Parse(strVal, CultureInfo.InvariantCulture);
}
catch (Exception e)
{
LogAttributeError(attribute, element, e);
}
}
return floatValue;
}
public static bool TryGetAttributeInt(this XElement element, string name, out int result)
{
var attribute = element?.GetAttribute(name);
result = default;
if (attribute == null) { return false; }
if (int.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var intVal))
{
result = intVal;
return true;
}
if (float.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var floatVal))
{
result = (int)floatVal;
return true;
}
return false;
}
public static int? GetAttributeNullableInt(this XElement element, string attributeName)
{
if (element.GetAttribute(attributeName) is XAttribute attribute)
{
// if there is an error in parsing, we will return the default value, which may cause edge case errors down the line
return GetAttributeInt(attribute, 0);
}
return null;
}
public static int GetAttributeInt(this XElement element, string name, int defaultValue) => GetAttributeInt(element?.GetAttribute(name), defaultValue);
public static int GetAttributeInt(this XAttribute attribute, int defaultValue)
{
if (attribute == null) { return defaultValue; }
int val = defaultValue;
try
{
if (!Int32.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out val))
{
val = (int)float.Parse(attribute.Value, CultureInfo.InvariantCulture);
}
}
catch (Exception e)
{
LogAttributeError(attribute, attribute.Parent, e);
}
return val;
}
public static uint GetAttributeUInt(this XElement element, string name, uint defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
uint val = defaultValue;
try
{
val = UInt32.Parse(attribute.Value);
}
catch (Exception e)
{
LogAttributeError(attribute, element, e);
}
return val;
}
public static ushort GetAttributeUInt16(this XElement element, string name, ushort defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
ushort val = defaultValue;
try
{
val = ushort.Parse(attribute.Value);
}
catch (Exception e)
{
LogAttributeError(attribute, element, e);
}
return val;
}
public static UInt64 GetAttributeUInt64(this XElement element, string name, UInt64 defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
UInt64 val = defaultValue;
try
{
val = UInt64.Parse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture);
}
catch (Exception e)
{
LogAttributeError(attribute, element, e);
}
return val;
}
public static Option<SerializableDateTime> GetAttributeDateTime(
this XElement element, string name)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return Option<SerializableDateTime>.None(); }
string attrVal = attribute.Value;
return SerializableDateTime.Parse(attrVal);
}
public static Version GetAttributeVersion(this XElement element, string name, Version defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
Version val = defaultValue;
try
{
val = Version.Parse(attribute.Value);
}
catch (Exception e)
{
LogAttributeError(attribute, element, e);
}
return val;
}
public static int[] GetAttributeIntArray(this XElement element, string name, int[] defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
string stringValue = attribute.Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
string[] splitValue = stringValue.Split(',');
int[] intValue = new int[splitValue.Length];
for (int i = 0; i < splitValue.Length; i++)
{
try
{
int val = Int32.Parse(splitValue[i]);
intValue[i] = val;
}
catch (Exception e)
{
LogAttributeError(attribute, element, e);
}
}
return intValue;
}
public static ushort[] GetAttributeUshortArray(this XElement element, string name, ushort[] defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
string stringValue = attribute.Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
string[] splitValue = stringValue.Split(',');
ushort[] ushortValue = new ushort[splitValue.Length];
for (int i = 0; i < splitValue.Length; i++)
{
try
{
ushort val = ushort.Parse(splitValue[i]);
ushortValue[i] = val;
}
catch (Exception e)
{
LogAttributeError(attribute, element, e);
}
}
return ushortValue;
}
public static T ParseEnumValue<T>(string value, T defaultValue, XAttribute attribute) where T : struct, Enum
{
if (Enum.TryParse(value, true, out T result))
{
return result;
}
else if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out int resultInt))
{
return Unsafe.As<int, T>(ref resultInt);
}
else
{
DebugConsole.ThrowError($"Error in {attribute}! \"{value}\" is not a valid {typeof(T).Name} value");
return defaultValue;
}
}
public static T GetAttributeEnum<T>(this XElement element, string name, T defaultValue) where T : struct, Enum
{
var attr = element?.GetAttribute(name);
if (attr == null) { return defaultValue; }
return ParseEnumValue<T>(attr.Value, defaultValue, attr);
}
[return: NotNullIfNotNull("defaultValue")]
public static T[] GetAttributeEnumArray<T>(this XElement element, string name, T[] defaultValue) where T : struct, Enum
{
string[] stringArray = element.GetAttributeStringArray(name, null);
if (stringArray == null) { return defaultValue; }
else if (stringArray.Length == 0) { return new T[0]; }
T[] enumArray = new T[stringArray.Length];
var attribute = element.GetAttribute(name);
for (int i = 0; i < stringArray.Length; i++)
{
try
{
enumArray[i] = ParseEnumValue<T>(stringArray[i].Trim(), default(T), attribute);
}
catch (Exception e)
{
LogAttributeError(attribute, element, e);
}
}
return enumArray;
}
public static bool GetAttributeBool(this XElement element, string name, bool defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
return attribute.GetAttributeBool(defaultValue);
}
public static bool GetAttributeBool(this XAttribute attribute, bool defaultValue)
{
if (attribute == null) { return defaultValue; }
string val = attribute.Value.ToLowerInvariant().Trim();
if (val == "true")
{
return true;
}
if (val == "false")
{
return false;
}
DebugConsole.ThrowError("Error in " + attribute.Value.ToString() + "! \"" + val + "\" is not a valid boolean value");
return false;
}
public static Point GetAttributePoint(this XElement element, string name, Point defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
return ParsePoint(attribute.Value);
}
public static Vector2 GetAttributeVector2(this XElement element, string name, Vector2 defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
return ParseVector2(attribute.Value);
}
public static Vector3 GetAttributeVector3(this XElement element, string name, Vector3 defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
return ParseVector3(attribute.Value);
}
public static Vector4 GetAttributeVector4(this XElement element, string name, Vector4 defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
return ParseVector4(attribute.Value);
}
public static Color GetAttributeColor(this XElement element, string name, Color defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
return ParseColor(attribute.Value);
}
public static Color? GetAttributeColor(this XElement element, string name)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return null; }
return ParseColor(attribute.Value);
}
public static Color[] GetAttributeColorArray(this XElement element, string name, Color[] defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
string stringValue = attribute.Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
string[] splitValue = stringValue.Split(';');
Color[] colorValue = new Color[splitValue.Length];
for (int i = 0; i < splitValue.Length; i++)
{
try
{
Color val = ParseColor(splitValue[i], true);
colorValue[i] = val;
}
catch (Exception e)
{
LogAttributeError(attribute, element, e);
}
}
return colorValue;
}
private static void LogAttributeError(XAttribute attribute, XElement element, Exception e)
{
string elementStr = element.ToString();
if (elementStr.Length > 500)
{
DebugConsole.ThrowError($"Error when reading attribute \"{attribute}\"!", e);
}
else
{
DebugConsole.ThrowError($"Error when reading attribute \"{attribute.Name}\" from {elementStr}!", e);
}
}
#if CLIENT
public static KeyOrMouse GetAttributeKeyOrMouse(this XElement element, string name, KeyOrMouse defaultValue)
{
string strValue = element.GetAttributeString(name, defaultValue?.ToString() ?? "");
if (Enum.TryParse(strValue, true, out Microsoft.Xna.Framework.Input.Keys key))
{
return key;
}
else if (Enum.TryParse(strValue, out MouseButton mouseButton))
{
return mouseButton;
}
else if (int.TryParse(strValue, NumberStyles.Any, CultureInfo.InvariantCulture, out int mouseButtonInt) &&
Enum.GetValues<MouseButton>().Contains((MouseButton)mouseButtonInt))
{
return (MouseButton)mouseButtonInt;
}
else if (string.Equals(strValue, "LeftMouse", StringComparison.OrdinalIgnoreCase))
{
return !PlayerInput.MouseButtonsSwapped() ? MouseButton.PrimaryMouse : MouseButton.SecondaryMouse;
}
else if (string.Equals(strValue, "RightMouse", StringComparison.OrdinalIgnoreCase))
{
return !PlayerInput.MouseButtonsSwapped() ? MouseButton.SecondaryMouse : MouseButton.PrimaryMouse;
}
return defaultValue;
}
#endif
public static Rectangle GetAttributeRect(this XElement element, string name, Rectangle defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
return ParseRect(attribute.Value, false);
}
//TODO: nested tuples and and n-uples where n!=2 are unsupported
public static (T1, T2) GetAttributeTuple<T1, T2>(this XElement element, string name, (T1, T2) defaultValue)
{
string strValue = element.GetAttributeString(name, $"({defaultValue.Item1}, {defaultValue.Item2})").Trim();
return ParseTuple(strValue, defaultValue);
}
public static (T1, T2)[] GetAttributeTupleArray<T1, T2>(this XElement element, string name,
(T1, T2)[] defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute == null) { return defaultValue; }
string stringValue = attribute.Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
return stringValue.Split(';').Select(s => ParseTuple<T1, T2>(s, default)).ToArray();
}
public static Range<int> GetAttributeRange(this XElement element, string name, Range<int> defaultValue)
{
var attribute = element?.GetAttribute(name);
if (attribute is null) { return defaultValue; }
string stringValue = attribute.Value;
return string.IsNullOrEmpty(stringValue) ? defaultValue : ParseRange(stringValue);
}
public static string ElementInnerText(this XElement el)
{
StringBuilder str = new StringBuilder();
foreach (XText textNode in el.DescendantNodes().OfType<XText>())
{
str.Append(textNode.Value);
}
return str.ToString();
}
public static string PointToString(Point point)
{
return point.X.ToString() + "," + point.Y.ToString();
}
public static string Vector2ToString(Vector2 vector)
{
return vector.X.ToString("G", CultureInfo.InvariantCulture) + "," + vector.Y.ToString("G", CultureInfo.InvariantCulture);
}
public static string Vector3ToString(Vector3 vector, string format = "G")
{
return vector.X.ToString(format, CultureInfo.InvariantCulture) + "," +
vector.Y.ToString(format, CultureInfo.InvariantCulture) + "," +
vector.Z.ToString(format, CultureInfo.InvariantCulture);
}
public static string Vector4ToString(Vector4 vector, string format = "G")
{
return vector.X.ToString(format, CultureInfo.InvariantCulture) + "," +
vector.Y.ToString(format, CultureInfo.InvariantCulture) + "," +
vector.Z.ToString(format, CultureInfo.InvariantCulture) + "," +
vector.W.ToString(format, CultureInfo.InvariantCulture);
}
[Obsolete("Prefer XMLExtensions.ToStringHex")]
public static string ColorToString(Color color)
=> $"{color.R},{color.G},{color.B},{color.A}";
public static string ToStringHex(this Color color)
=> $"#{color.R:X2}{color.G:X2}{color.B:X2}"
+ ((color.A < 255) ? $"{color.A:X2}" : "");
public static string RectToString(Rectangle rect)
{
return rect.X + "," + rect.Y + "," + rect.Width + "," + rect.Height;
}
public static (T1, T2) ParseTuple<T1, T2>(string strValue, (T1, T2) defaultValue)
{
strValue = strValue.Trim();
//require parentheses
if (strValue[0] != '(' || strValue[^1] != ')') { return defaultValue; }
//remove parentheses
strValue = strValue[1..^1];
string[] elems = strValue.Split(',');
if (elems.Length != 2) { return defaultValue; }
return ((T1)Converters[typeof(T1)].Invoke(elems[0], defaultValue.Item1),
(T2)Converters[typeof(T2)].Invoke(elems[1], defaultValue.Item2));
}
public static Point ParsePoint(string stringPoint, bool errorMessages = true)
{
string[] components = stringPoint.Split(',');
Point point = Point.Zero;
if (components.Length != 2)
{
if (!errorMessages) { return point; }
DebugConsole.ThrowError("Failed to parse the string \"" + stringPoint + "\" to Vector2");
return point;
}
int.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out point.X);
int.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out point.Y);
return point;
}
public static Vector2 ParseVector2(string stringVector2, bool errorMessages = true)
{
string[] components = stringVector2.Split(',');
Vector2 vector = Vector2.Zero;
if (components.Length != 2)
{
if (!errorMessages) { return vector; }
DebugConsole.ThrowError("Failed to parse the string \"" + stringVector2 + "\" to Vector2");
return vector;
}
float.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.X);
float.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Y);
return vector;
}
public static Vector3 ParseVector3(string stringVector3, bool errorMessages = true)
{
string[] components = stringVector3.Split(',');
Vector3 vector = Vector3.Zero;
if (components.Length != 3)
{
if (!errorMessages) { return vector; }
DebugConsole.ThrowError("Failed to parse the string \"" + stringVector3 + "\" to Vector3");
return vector;
}
Single.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.X);
Single.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Y);
Single.TryParse(components[2], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Z);
return vector;
}
public static Vector4 ParseVector4(string stringVector4, bool errorMessages = true)
{
string[] components = stringVector4.Split(',');
Vector4 vector = Vector4.Zero;
if (components.Length < 3)
{
if (errorMessages) { DebugConsole.ThrowError("Failed to parse the string \"" + stringVector4 + "\" to Vector4"); }
return vector;
}
Single.TryParse(components[0], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.X);
Single.TryParse(components[1], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.Y);
Single.TryParse(components[2], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.Z);
if (components.Length > 3)
{
Single.TryParse(components[3], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.W);
}
return vector;
}
private static readonly ImmutableDictionary<Identifier, Color> monoGameColors =
typeof(Color)
.GetProperties(BindingFlags.Static | BindingFlags.Public)
.Where(p => p.PropertyType == typeof(Color))
.Select(p => (p.Name.ToIdentifier(), p.GetValueFromStaticProperty<Color>()))
.ToImmutableDictionary();
public static Color ParseColor(string stringColor, bool errorMessages = true)
{
if (stringColor.StartsWith("gui.", StringComparison.OrdinalIgnoreCase))
{
#if CLIENT
Identifier colorName = stringColor.Substring(4).ToIdentifier();
if (GUIStyle.Colors.TryGetValue(colorName, out GUIColor guiColor))
{
return guiColor.Value;
}
#endif
return Color.White;
}
if (stringColor.StartsWith("faction.", StringComparison.OrdinalIgnoreCase))
{
Identifier factionId = stringColor.Substring(8).ToIdentifier();
if (FactionPrefab.Prefabs.TryGet(factionId, out var faction))
{
return faction.IconColor;
}
return Color.White;
}
if (monoGameColors.TryGetValue(stringColor.ToIdentifier(), out var monoGameColor))
{
return monoGameColor;
}
string[] strComponents = stringColor.Split(',');
Color color = Color.White;
float[] components = new float[4] { 1.0f, 1.0f, 1.0f, 1.0f };
if (strComponents.Length == 1)
{
bool altParseFailed = true;
stringColor = stringColor.Trim();
if (stringColor.Length > 0 && stringColor[0] == '#')
{
stringColor = stringColor.Substring(1);
if (int.TryParse(stringColor, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int colorInt))
{
if (stringColor.Length == 6)
{
colorInt = (colorInt << 8) | 0xff;
}
components[0] = ((float)((colorInt & 0xff000000) >> 24)) / 255.0f;
components[1] = ((float)((colorInt & 0x00ff0000) >> 16)) / 255.0f;
components[2] = ((float)((colorInt & 0x0000ff00) >> 8)) / 255.0f;
components[3] = ((float)(colorInt & 0x000000ff)) / 255.0f;
altParseFailed = false;
}
}
else if (stringColor.Length > 0 && stringColor[0] == '{')
{
stringColor = stringColor.Substring(1, stringColor.Length-2);
string[] mgComponents = stringColor.Split(' ');
if (mgComponents.Length == 4)
{
altParseFailed = false;
string[] expectedPrefixes = {"R:", "G:", "B:", "A:"};
for (int i = 0; i < 4; i++)
{
if (mgComponents[i].StartsWith(expectedPrefixes[i], StringComparison.OrdinalIgnoreCase))
{
string strToParse = mgComponents[i]
.Remove(expectedPrefixes[i], StringComparison.OrdinalIgnoreCase)
.Trim();
int val = 0;
altParseFailed |= !int.TryParse(strToParse, out val);
components[i] = ((float) val) / 255f;
}
else
{
altParseFailed = true;
break;
}
}
}
}
if (altParseFailed)
{
if (errorMessages) { DebugConsole.ThrowError("Failed to parse the string \"" + stringColor + "\" to Color"); }
return Color.White;
}
}
else
{
for (int i = 0; i < 4 && i < strComponents.Length; i++)
{
float.TryParse(strComponents[i], NumberStyles.Float, CultureInfo.InvariantCulture, out components[i]);
}
if (components.Any(c => c > 1.0f))
{
for (int i = 0; i < 4; i++)
{
components[i] = components[i] / 255.0f;
}
//alpha defaults to 1.0 if not given
if (strComponents.Length < 4) components[3] = 1.0f;
}
}
return new Color(components[0], components[1], components[2], components[3]);
}
public static Rectangle ParseRect(string stringRect, bool requireSize, bool errorMessages = true)
{
string[] strComponents = stringRect.Split(',');
if ((strComponents.Length < 3 && requireSize) || strComponents.Length < 2)
{
if (errorMessages) { DebugConsole.ThrowError("Failed to parse the string \"" + stringRect + "\" to Rectangle"); }
return new Rectangle(0, 0, 0, 0);
}
int[] components = new int[4] { 0, 0, 0, 0 };
for (int i = 0; i < 4 && i < strComponents.Length; i++)
{
int.TryParse(strComponents[i], out components[i]);
}
return new Rectangle(components[0], components[1], components[2], components[3]);
}
public static float[] ParseFloatArray(string[] stringArray)
{
if (stringArray == null || stringArray.Length == 0) return null;
float[] floatArray = new float[stringArray.Length];
for (int i = 0; i < floatArray.Length; i++)
{
floatArray[i] = 0.0f;
Single.TryParse(stringArray[i], NumberStyles.Float, CultureInfo.InvariantCulture, out floatArray[i]);
}
return floatArray;
}
// parse a range string, e.g "1-3" or "3"
public static Range<int> ParseRange(string rangeString)
{
if (string.IsNullOrWhiteSpace(rangeString)) { return GetDefault(rangeString); }
string[] split = rangeString.Split('-');
return split.Length switch
{
1 when TryParseInt(split[0], out int value) => new Range<int>(value, value),
2 when TryParseInt(split[0], out int min) && TryParseInt(split[1], out int max) && min < max => new Range<int>(min, max),
_ => GetDefault(rangeString)
};
static bool TryParseInt(string value, out int result)
{
if (!string.IsNullOrWhiteSpace(value))
{
return int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
}
result = default;
return false;
}
static Range<int> GetDefault(string rangeString)
{
DebugConsole.ThrowError($"Error parsing range: \"{rangeString}\" (using default value 0-99)");
return new Range<int>(0, 99);
}
}
public static Identifier VariantOf(this XElement element) =>
element.GetAttributeIdentifier("inherit", element.GetAttributeIdentifier("variantof", ""));
public static bool IsOverride(this XElement element) => element.NameAsIdentifier() == "override";
/// <summary>
/// Get the root element of the document, or the first child element of the root if it's an override element.
/// Or in other words, the "rootmost element that actually contains some content".
/// </summary>
public static XElement GetRootExcludingOverride(this XDocument doc) => doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
public static XElement FirstElement(this XElement element) => element.Elements().FirstOrDefault();
public static XAttribute GetAttribute(this XElement element, string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => element.GetAttribute(a => a.Name.ToString().Equals(name, comparisonMethod));
public static bool TrySetAttributeValue(this XElement element, string name, object value, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase)
{
var attribute = GetAttribute(element, name, comparisonMethod);
if (attribute == null) { return false; }
attribute.SetValue(value);
return true;
}
public static void SetAttribute(this XElement element, string name, object value)
{
if (!TrySetAttributeValue(element, name, value))
{
element.SetAttributeValue(name, value);
}
}
public static XAttribute GetAttribute(this XElement element, Identifier name) => element.GetAttribute(name.Value, StringComparison.OrdinalIgnoreCase);
public static XAttribute GetAttribute(this XElement element, Func<XAttribute, bool> predicate) => element.Attributes().FirstOrDefault(predicate);
/// <summary>
/// Returns the first child element that matches the name using the provided comparison method.
/// </summary>
public static XElement GetChildElement(this XContainer container, string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => container.Elements().FirstOrDefault(e => e.Name.ToString().Equals(name, comparisonMethod));
/// <summary>
/// Returns all child elements that match the name using the provided comparison method.
/// </summary>
public static IEnumerable<XElement> GetChildElements(this XContainer container, string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => container.Elements().Where(e => e.Name.ToString().Equals(name, comparisonMethod));
public static IEnumerable<XElement> GetChildElements(this XContainer container, params string[] names)
{
return names.SelectMany(name => container.GetChildElements(name));
}
public static bool ComesAfter(this XElement element, XElement other)
{
if (element.Parent != other.Parent) { return false; }
foreach (var child in element.Parent.Elements())
{
if (child == element) { return false; }
if (child == other) { return true; }
}
return false;
}
public static Identifier NameAsIdentifier(this XElement elem)
{
return elem.Name.LocalName.ToIdentifier();
}
public static Identifier NameAsIdentifier(this XAttribute attr)
{
return attr.Name.LocalName.ToIdentifier();
}
}
}