1 using Microsoft.Xna.Framework;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.Diagnostics;
6 using System.Diagnostics.CodeAnalysis;
7 using System.Globalization;
9 using System.Reflection;
10 using System.Runtime.CompilerServices;
13 using System.Xml.Linq;
21 public static class XMLExtensions
23 private readonly
static ImmutableDictionary<Type, Func<string, object, object>> Converters
24 =
new Dictionary<Type, Func<string, object, object>>()
26 { typeof(
string), (str, defVal) => str },
27 { typeof(
int), (str, defVal) =>
int.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out
int result) ? result : defVal },
28 { typeof(uint), (str, defVal) => uint.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out uint result) ? result : defVal },
29 { typeof(UInt64), (str, defVal) => UInt64.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out UInt64 result) ? result : defVal },
30 { typeof(
float), (str, defVal) =>
float.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out
float result) ? result : defVal },
31 { typeof(
bool), (str, defVal) =>
bool.TryParse(str, out
bool result) ? result : defVal },
32 { typeof(Color), (str, defVal) => ParseColor(str) },
33 { typeof(Vector2), (str, defVal) => ParseVector2(str) },
34 { typeof(Vector3), (str, defVal) => ParseVector3(str) },
35 { typeof(Vector4), (str, defVal) => ParseVector4(str) },
36 { typeof(Rectangle), (str, defVal) => ParseRect(str,
true) }
37 }.ToImmutableDictionary();
39 public static string ParseContentPathFromUri(
this XObject element)
40 => !
string.IsNullOrWhiteSpace(element.BaseUri)
41 ? System.
IO.
Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri.CleanUpPath())
44 public static readonly XmlReaderSettings ReaderSettings =
new XmlReaderSettings
46 DtdProcessing = DtdProcessing.Prohibit,
48 IgnoreWhitespace =
true,
51 public static XmlReader CreateReader(System.IO.Stream stream,
string baseUri =
"")
52 => XmlReader.Create(stream, ReaderSettings, baseUri);
54 public static XDocument TryLoadXml(System.IO.Stream stream)
59 using XmlReader reader = CreateReader(stream);
60 doc = XDocument.Load(reader);
64 DebugConsole.ThrowError($
"Couldn't load xml document from stream!", e);
67 if (doc?.Root ==
null)
69 DebugConsole.ThrowError(
"XML could not be loaded from stream: Document or the root element is invalid!");
75 public static XDocument TryLoadXml(ContentPath path) => TryLoadXml(path.Value);
77 public static XDocument TryLoadXml(
string filePath)
79 var doc = TryLoadXml(filePath, out var exception);
80 if (exception !=
null)
82 DebugConsole.ThrowError($
"Couldn't load xml document \"{filePath}\"!", exception);
86 DebugConsole.ThrowError($
"File \"{filePath}\" could not be loaded: Document or the root element is invalid!");
91 public static XDocument TryLoadXml(
string filePath, out Exception exception)
97 ToolBox.IsProperFilenameCase(filePath);
98 using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read, catchUnauthorizedAccessExceptions:
false);
99 using XmlReader reader = CreateReader(stream, Path.GetFullPath(filePath));
100 doc = XDocument.Load(reader, LoadOptions.SetBaseUri);
107 if (doc?.Root ==
null)
114 public static object GetAttributeObject(XAttribute attribute)
116 if (attribute ==
null) {
return null; }
118 return ParseToObject(attribute.Value.ToString());
121 public static object ParseToObject(
string value)
123 if (value.Contains(
".") && Single.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out
float floatVal))
127 if (Int32.TryParse(value, out
int intVal))
132 string lowerTrimmedVal = value.ToLowerInvariant().Trim();
133 if (lowerTrimmedVal ==
"true")
137 if (lowerTrimmedVal ==
"false")
146 public static string GetAttributeString(
this XElement element,
string name,
string defaultValue)
148 var attribute = element?.GetAttribute(name);
149 if (attribute ==
null) {
return defaultValue; }
150 string str = GetAttributeString(attribute, defaultValue);
152 if (!str.IsNullOrEmpty() &&
153 (str.Contains(
"%ModDir", StringComparison.OrdinalIgnoreCase)
154 || str.CleanUpPathCrossPlatform(correctFilenameCase:
false).StartsWith(
"Content/", StringComparison.OrdinalIgnoreCase)))
156 DebugConsole.ThrowError($
"Use {nameof(GetAttributeContentPath)} instead of {nameof(GetAttributeString)}\n{Environment.StackTrace.CleanupStackTrace()}");
157 if (Debugger.IsAttached) { Debugger.Break(); }
163 public static string GetAttributeStringUnrestricted(
this XElement element,
string name,
string defaultValue)
165 #warning TODO: remove?
166 var attribute = element?.GetAttribute(name);
167 if (attribute ==
null) {
return defaultValue; }
168 return GetAttributeString(attribute, defaultValue);
171 public static bool DoesAttributeReferenceFileNameAlone(
this XElement element,
string name)
173 string texName = element.GetAttributeStringUnrestricted(name,
"");
174 return !texName.IsNullOrEmpty() & !texName.Contains(
"/") && !texName.Contains(
"%ModDir", StringComparison.OrdinalIgnoreCase);
177 public static ContentPath GetAttributeContentPath(
this XElement element,
string name, ContentPackage contentPackage)
179 var attribute = element?.GetAttribute(name);
180 if (attribute ==
null) {
return null; }
181 return ContentPath.FromRaw(contentPackage, GetAttributeString(attribute,
null));
184 public static Identifier GetAttributeIdentifier(
this XElement element,
string name,
string defaultValue)
186 return element.GetAttributeString(name, defaultValue).ToIdentifier();
189 public static Identifier GetAttributeIdentifier(
this XElement element,
string name, Identifier defaultValue)
191 return element.GetAttributeIdentifier(name, defaultValue.Value);
194 private static string GetAttributeString(XAttribute attribute,
string defaultValue)
196 string value = attribute.Value;
197 return string.IsNullOrEmpty(value) ? defaultValue : value;
200 public static string[] GetAttributeStringArray(
this XElement element,
string name,
string[] defaultValue,
bool trim =
true,
bool convertToLowerInvariant =
false)
202 var attribute = element?.GetAttribute(name);
203 if (attribute ==
null) {
return defaultValue; }
205 string stringValue = attribute.Value;
206 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
208 string[] splitValue = stringValue.Split(
',',
',');
210 for (
int i = 0; i < splitValue.Length; i++)
212 if (convertToLowerInvariant) { splitValue[i] = splitValue[i].ToLowerInvariant(); }
213 if (trim) { splitValue[i] = splitValue[i].Trim(); }
219 public static Identifier[] GetAttributeIdentifierArray(
this XElement element, Identifier[] defaultValue, params
string[] matchingAttributeName)
221 if (element ==
null) {
return defaultValue; }
222 foreach (
string name
in matchingAttributeName)
224 var value = element.GetAttributeIdentifierArray(name, defaultValue);
225 if (value != defaultValue) {
return value; }
230 public static Identifier[] GetAttributeIdentifierArray(
this XElement element,
string name, Identifier[] defaultValue,
bool trim =
true)
232 return element.GetAttributeStringArray(name,
null, trim: trim, convertToLowerInvariant:
false)
237 public static ImmutableHashSet<Identifier> GetAttributeIdentifierImmutableHashSet(
this XElement element,
string key, ImmutableHashSet<Identifier> defaultValue,
bool trim =
true)
239 return element.GetAttributeIdentifierArray(key,
null, trim)?.ToImmutableHashSet()
243 public static float GetAttributeFloat(
this XElement element,
float defaultValue, params
string[] matchingAttributeName)
245 if (element ==
null) {
return defaultValue; }
247 foreach (
string name
in matchingAttributeName)
249 var attribute = element.GetAttribute(name);
250 if (attribute ==
null) {
continue; }
251 return GetAttributeFloat(attribute, defaultValue);
257 public static float GetAttributeFloat(
this XElement element,
string name,
float defaultValue) => GetAttributeFloat(element?.GetAttribute(name), defaultValue);
259 public static float GetAttributeFloat(
this XAttribute attribute,
float defaultValue)
261 if (attribute ==
null) {
return defaultValue; }
263 float val = defaultValue;
267 string strVal = attribute.Value;
268 if (strVal.LastOrDefault() ==
'f')
270 strVal = strVal.Substring(0, strVal.Length - 1);
272 val =
float.Parse(strVal, CultureInfo.InvariantCulture);
276 DebugConsole.ThrowError(
"Error in " + attribute +
"! ", e);
282 public static double GetAttributeDouble(
this XElement element,
string name,
double defaultValue) => GetAttributeDouble(element?.GetAttribute(name), defaultValue);
284 public static double GetAttributeDouble(
this XAttribute attribute,
double defaultValue)
286 if (attribute ==
null) {
return defaultValue; }
288 double val = defaultValue;
291 string strVal = attribute.Value;
292 if (strVal.LastOrDefault() ==
'f')
294 strVal = strVal.Substring(0, strVal.Length - 1);
296 val =
double.Parse(strVal, CultureInfo.InvariantCulture);
300 DebugConsole.ThrowError(
"Error in " + attribute +
"!", e);
307 public static float[] GetAttributeFloatArray(
this XElement element,
string name,
float[] defaultValue)
309 var attribute = element?.GetAttribute(name);
310 if (attribute ==
null) {
return defaultValue; }
312 string stringValue = attribute.Value;
313 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
315 string[] splitValue = stringValue.Split(
',');
316 float[] floatValue =
new float[splitValue.Length];
317 for (
int i = 0; i < splitValue.Length; i++)
321 string strVal = splitValue[i];
322 if (strVal.LastOrDefault() ==
'f')
324 strVal = strVal.Substring(0, strVal.Length - 1);
326 floatValue[i] =
float.Parse(strVal, CultureInfo.InvariantCulture);
330 LogAttributeError(attribute, element, e);
337 public static bool TryGetAttributeInt(
this XElement element,
string name, out
int result)
339 var attribute = element?.GetAttribute(name);
341 if (attribute ==
null) {
return false; }
343 if (
int.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var intVal))
348 if (
float.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var floatVal))
350 result = (int)floatVal;
356 public static int GetAttributeInt(
this XElement element,
string name,
int defaultValue) => GetAttributeInt(element?.GetAttribute(name), defaultValue);
358 public static int GetAttributeInt(
this XAttribute attribute,
int defaultValue)
360 if (attribute ==
null) {
return defaultValue; }
362 int val = defaultValue;
366 if (!Int32.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out val))
368 val = (int)
float.Parse(attribute.Value, CultureInfo.InvariantCulture);
373 LogAttributeError(attribute, attribute.Parent, e);
379 public static uint GetAttributeUInt(
this XElement element,
string name, uint defaultValue)
381 var attribute = element?.GetAttribute(name);
382 if (attribute ==
null) {
return defaultValue; }
384 uint val = defaultValue;
388 val = UInt32.Parse(attribute.Value);
392 LogAttributeError(attribute, element, e);
398 public static ushort GetAttributeUInt16(
this XElement element,
string name, ushort defaultValue)
400 var attribute = element?.GetAttribute(name);
401 if (attribute ==
null) {
return defaultValue; }
403 ushort val = defaultValue;
407 val = ushort.Parse(attribute.Value);
411 LogAttributeError(attribute, element, e);
417 public static UInt64 GetAttributeUInt64(
this XElement element,
string name, UInt64 defaultValue)
419 var attribute = element?.GetAttribute(name);
420 if (attribute ==
null) {
return defaultValue; }
422 UInt64 val = defaultValue;
426 val = UInt64.Parse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture);
430 LogAttributeError(attribute, element, e);
436 public static Option<SerializableDateTime> GetAttributeDateTime(
437 this XElement element,
string name)
439 var attribute = element?.GetAttribute(name);
440 if (attribute ==
null) {
return Option<SerializableDateTime>.None(); }
442 string attrVal = attribute.Value;
443 return SerializableDateTime.Parse(attrVal);
446 public static Version GetAttributeVersion(
this XElement element,
string name, Version defaultValue)
448 var attribute = element?.GetAttribute(name);
449 if (attribute ==
null) {
return defaultValue; }
451 Version val = defaultValue;
455 val = Version.Parse(attribute.Value);
459 LogAttributeError(attribute, element, e);
465 public static int[] GetAttributeIntArray(
this XElement element,
string name,
int[] defaultValue)
467 var attribute = element?.GetAttribute(name);
468 if (attribute ==
null) {
return defaultValue; }
470 string stringValue = attribute.Value;
471 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
473 string[] splitValue = stringValue.Split(
',');
474 int[] intValue =
new int[splitValue.Length];
475 for (
int i = 0; i < splitValue.Length; i++)
479 int val = Int32.Parse(splitValue[i]);
484 LogAttributeError(attribute, element, e);
490 public static ushort[] GetAttributeUshortArray(
this XElement element,
string name, ushort[] defaultValue)
492 var attribute = element?.GetAttribute(name);
493 if (attribute ==
null) {
return defaultValue; }
495 string stringValue = attribute.Value;
496 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
498 string[] splitValue = stringValue.Split(
',');
499 ushort[] ushortValue =
new ushort[splitValue.Length];
500 for (
int i = 0; i < splitValue.Length; i++)
504 ushort val = ushort.Parse(splitValue[i]);
505 ushortValue[i] = val;
509 LogAttributeError(attribute, element, e);
516 private static T ParseEnumValue<T>(
string value, T defaultValue, XAttribute attribute) where T :
struct, Enum
518 if (Enum.TryParse(value,
true, out T result))
522 else if (
int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out
int resultInt))
524 return Unsafe.As<int, T>(ref resultInt);
528 DebugConsole.ThrowError($
"Error in {attribute}! \"{value}\" is not a valid {typeof(T).Name} value");
533 public static T GetAttributeEnum<T>(
this XElement element,
string name, T defaultValue) where T :
struct, Enum
535 var attr = element?.GetAttribute(name);
536 if (attr ==
null) {
return defaultValue; }
537 return ParseEnumValue<T>(attr.Value, defaultValue, attr);
540 [
return: NotNullIfNotNull(
"defaultValue")]
541 public static T[] GetAttributeEnumArray<T>(
this XElement element,
string name, T[] defaultValue) where T :
struct, Enum
543 string[] stringArray = element.GetAttributeStringArray(name,
null);
544 if (stringArray ==
null) {
return defaultValue; }
545 else if (stringArray.Length == 0) {
return new T[0]; }
547 T[] enumArray =
new T[stringArray.Length];
548 var attribute = element.GetAttribute(name);
550 for (
int i = 0; i < stringArray.Length; i++)
554 enumArray[i] = ParseEnumValue<T>(stringArray[i].Trim(),
default(T), attribute);
558 LogAttributeError(attribute, element, e);
565 public static bool GetAttributeBool(
this XElement element,
string name,
bool defaultValue)
567 var attribute = element?.GetAttribute(name);
568 if (attribute ==
null) {
return defaultValue; }
569 return attribute.GetAttributeBool(defaultValue);
572 public static bool GetAttributeBool(
this XAttribute attribute,
bool defaultValue)
574 if (attribute ==
null) {
return defaultValue; }
576 string val = attribute.Value.ToLowerInvariant().Trim();
586 DebugConsole.ThrowError(
"Error in " + attribute.Value.ToString() +
"! \"" + val +
"\" is not a valid boolean value");
590 public static Point GetAttributePoint(
this XElement element,
string name, Point defaultValue)
592 var attribute = element?.GetAttribute(name);
593 if (attribute ==
null) {
return defaultValue; }
594 return ParsePoint(attribute.Value);
597 public static Vector2 GetAttributeVector2(
this XElement element,
string name, Vector2 defaultValue)
599 var attribute = element?.GetAttribute(name);
600 if (attribute ==
null) {
return defaultValue; }
601 return ParseVector2(attribute.Value);
604 public static Vector3 GetAttributeVector3(
this XElement element,
string name, Vector3 defaultValue)
606 var attribute = element?.GetAttribute(name);
607 if (attribute ==
null) {
return defaultValue; }
608 return ParseVector3(attribute.Value);
611 public static Vector4 GetAttributeVector4(
this XElement element,
string name, Vector4 defaultValue)
613 var attribute = element?.GetAttribute(name);
614 if (attribute ==
null) {
return defaultValue; }
615 return ParseVector4(attribute.Value);
618 public static Color GetAttributeColor(
this XElement element,
string name, Color defaultValue)
620 var attribute = element?.GetAttribute(name);
621 if (attribute ==
null) {
return defaultValue; }
622 return ParseColor(attribute.Value);
625 public static Color? GetAttributeColor(
this XElement element,
string name)
627 var attribute = element?.GetAttribute(name);
628 if (attribute ==
null) {
return null; }
629 return ParseColor(attribute.Value);
632 public static Color[] GetAttributeColorArray(
this XElement element,
string name, Color[] defaultValue)
634 var attribute = element?.GetAttribute(name);
635 if (attribute ==
null) {
return defaultValue; }
637 string stringValue = attribute.Value;
638 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
640 string[] splitValue = stringValue.Split(
';');
641 Color[] colorValue =
new Color[splitValue.Length];
642 for (
int i = 0; i < splitValue.Length; i++)
646 Color val = ParseColor(splitValue[i],
true);
651 LogAttributeError(attribute, element, e);
658 private static void LogAttributeError(XAttribute attribute, XElement element, Exception e)
660 string elementStr = element.ToString();
661 if (elementStr.Length > 500)
663 DebugConsole.ThrowError($
"Error when reading attribute \"{attribute}\"!", e);
667 DebugConsole.ThrowError($
"Error when reading attribute \"{attribute.Name}\" from {elementStr}!", e);
672 public static KeyOrMouse GetAttributeKeyOrMouse(
this XElement element,
string name, KeyOrMouse defaultValue)
674 string strValue = element.GetAttributeString(name, defaultValue?.ToString() ??
"");
675 if (Enum.TryParse(strValue,
true, out Microsoft.Xna.Framework.Input.Keys key))
679 else if (Enum.TryParse(strValue, out
MouseButton mouseButton))
683 else if (
int.TryParse(strValue, NumberStyles.Any, CultureInfo.InvariantCulture, out
int mouseButtonInt) &&
688 else if (
string.Equals(strValue,
"LeftMouse", StringComparison.OrdinalIgnoreCase))
692 else if (
string.Equals(strValue,
"RightMouse", StringComparison.OrdinalIgnoreCase))
700 public static Rectangle GetAttributeRect(
this XElement element,
string name, Rectangle defaultValue)
702 var attribute = element?.GetAttribute(name);
703 if (attribute ==
null) {
return defaultValue; }
704 return ParseRect(attribute.Value,
false);
708 public static (T1, T2) GetAttributeTuple<T1, T2>(
this XElement element,
string name, (T1, T2) defaultValue)
710 string strValue = element.GetAttributeString(name, $
"({defaultValue.Item1}, {defaultValue.Item2})").Trim();
712 return ParseTuple(strValue, defaultValue);
715 public static (T1, T2)[] GetAttributeTupleArray<T1, T2>(
this XElement element,
string name,
716 (T1, T2)[] defaultValue)
718 var attribute = element?.GetAttribute(name);
719 if (attribute ==
null) {
return defaultValue; }
721 string stringValue = attribute.Value;
722 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
724 return stringValue.Split(
';').Select(s => ParseTuple<T1, T2>(s,
default)).ToArray();
727 public static Range<int> GetAttributeRange(
this XElement element,
string name, Range<int> defaultValue)
729 var attribute = element?.GetAttribute(name);
730 if (attribute is
null) {
return defaultValue; }
732 string stringValue = attribute.Value;
733 return string.IsNullOrEmpty(stringValue) ? defaultValue : ParseRange(stringValue);
736 public static string ElementInnerText(
this XElement el)
738 StringBuilder str =
new StringBuilder();
739 foreach (XNode element
in el.DescendantNodes().Where(x => x.NodeType == XmlNodeType.Text))
741 str.Append(element.ToString());
743 return str.ToString();
746 public static string PointToString(Point point)
748 return point.X.ToString() +
"," + point.Y.ToString();
751 public static string Vector2ToString(Vector2 vector)
753 return vector.X.ToString(
"G", CultureInfo.InvariantCulture) +
"," + vector.Y.ToString(
"G", CultureInfo.InvariantCulture);
756 public static string Vector3ToString(Vector3 vector,
string format =
"G")
758 return vector.X.ToString(format, CultureInfo.InvariantCulture) +
"," +
759 vector.Y.ToString(format, CultureInfo.InvariantCulture) +
"," +
760 vector.Z.ToString(format, CultureInfo.InvariantCulture);
763 public static string Vector4ToString(Vector4 vector,
string format =
"G")
765 return vector.X.ToString(format, CultureInfo.InvariantCulture) +
"," +
766 vector.Y.ToString(format, CultureInfo.InvariantCulture) +
"," +
767 vector.Z.ToString(format, CultureInfo.InvariantCulture) +
"," +
768 vector.W.ToString(format, CultureInfo.InvariantCulture);
771 [Obsolete(
"Prefer XMLExtensions.ToStringHex")]
772 public static string ColorToString(Color color)
773 => $
"{color.R},{color.G},{color.B},{color.A}";
775 public static string ToStringHex(
this Color color)
776 => $
"#{color.R:X2}{color.G:X2}{color.B:X2}"
777 + ((color.A < 255) ? $
"{color.A:X2}" :
"");
779 public static string RectToString(Rectangle rect)
781 return rect.X +
"," + rect.Y +
"," + rect.Width +
"," + rect.Height;
784 public static (T1, T2) ParseTuple<T1, T2>(
string strValue, (T1, T2) defaultValue)
786 strValue = strValue.Trim();
788 if (strValue[0] !=
'(' || strValue[^1] !=
')') {
return defaultValue; }
790 strValue = strValue[1..^1];
792 string[] elems = strValue.Split(
',');
793 if (elems.Length != 2) {
return defaultValue; }
795 return ((T1)Converters[typeof(T1)].Invoke(elems[0], defaultValue.Item1),
796 (T2)Converters[typeof(T2)].Invoke(elems[1], defaultValue.Item2));
799 public static Point ParsePoint(
string stringPoint,
bool errorMessages =
true)
801 string[] components = stringPoint.Split(
',');
802 Point point = Point.Zero;
804 if (components.Length != 2)
806 if (!errorMessages) {
return point; }
807 DebugConsole.ThrowError(
"Failed to parse the string \"" + stringPoint +
"\" to Vector2");
811 int.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out point.X);
812 int.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out point.Y);
816 public static Vector2 ParseVector2(
string stringVector2,
bool errorMessages =
true)
818 string[] components = stringVector2.Split(
',');
820 Vector2 vector = Vector2.Zero;
822 if (components.Length != 2)
824 if (!errorMessages) {
return vector; }
825 DebugConsole.ThrowError(
"Failed to parse the string \"" + stringVector2 +
"\" to Vector2");
829 float.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.X);
830 float.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Y);
835 public static Vector3 ParseVector3(
string stringVector3,
bool errorMessages =
true)
837 string[] components = stringVector3.Split(
',');
839 Vector3 vector = Vector3.Zero;
841 if (components.Length != 3)
843 if (!errorMessages) {
return vector; }
844 DebugConsole.ThrowError(
"Failed to parse the string \"" + stringVector3 +
"\" to Vector3");
848 Single.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.X);
849 Single.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Y);
850 Single.TryParse(components[2], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Z);
855 public static Vector4 ParseVector4(
string stringVector4,
bool errorMessages =
true)
857 string[] components = stringVector4.Split(
',');
859 Vector4 vector = Vector4.Zero;
861 if (components.Length < 3)
863 if (errorMessages) { DebugConsole.ThrowError(
"Failed to parse the string \"" + stringVector4 +
"\" to Vector4"); }
867 Single.TryParse(components[0], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.X);
868 Single.TryParse(components[1], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.Y);
869 Single.TryParse(components[2], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.Z);
870 if (components.Length > 3)
872 Single.TryParse(components[3], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.W);
878 private static readonly ImmutableDictionary<Identifier, Color> monoGameColors =
880 .GetProperties(BindingFlags.Static | BindingFlags.Public)
881 .Where(p => p.PropertyType == typeof(Color))
882 .Select(p => (p.Name.ToIdentifier(), p.GetValueFromStaticProperty<Color>()))
883 .ToImmutableDictionary();
885 public static Color ParseColor(
string stringColor,
bool errorMessages =
true)
887 if (stringColor.StartsWith(
"gui.", StringComparison.OrdinalIgnoreCase))
890 Identifier colorName = stringColor.Substring(4).ToIdentifier();
891 if (GUIStyle.Colors.TryGetValue(colorName, out GUIColor guiColor))
893 return guiColor.Value;
898 if (stringColor.StartsWith(
"faction.", StringComparison.OrdinalIgnoreCase))
900 Identifier factionId = stringColor.Substring(8).ToIdentifier();
901 if (FactionPrefab.Prefabs.TryGet(factionId, out var faction))
903 return faction.IconColor;
908 if (monoGameColors.TryGetValue(stringColor.ToIdentifier(), out var monoGameColor))
910 return monoGameColor;
913 string[] strComponents = stringColor.Split(
',');
915 Color color = Color.White;
917 float[] components =
new float[4] { 1.0f, 1.0f, 1.0f, 1.0f };
919 if (strComponents.Length == 1)
921 bool altParseFailed =
true;
922 stringColor = stringColor.Trim();
923 if (stringColor.Length > 0 && stringColor[0] ==
'#')
925 stringColor = stringColor.Substring(1);
927 if (
int.TryParse(stringColor, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out
int colorInt))
929 if (stringColor.Length == 6)
931 colorInt = (colorInt << 8) | 0xff;
933 components[0] = ((float)((colorInt & 0xff000000) >> 24)) / 255.0f;
934 components[1] = ((float)((colorInt & 0x00ff0000) >> 16)) / 255.0f;
935 components[2] = ((float)((colorInt & 0x0000ff00) >> 8)) / 255.0f;
936 components[3] = ((float)(colorInt & 0x000000ff)) / 255.0f;
938 altParseFailed =
false;
941 else if (stringColor.Length > 0 && stringColor[0] ==
'{')
943 stringColor = stringColor.Substring(1, stringColor.Length-2);
945 string[] mgComponents = stringColor.Split(
' ');
946 if (mgComponents.Length == 4)
948 altParseFailed =
false;
950 string[] expectedPrefixes = {
"R:",
"G:",
"B:",
"A:"};
951 for (
int i = 0; i < 4; i++)
953 if (mgComponents[i].StartsWith(expectedPrefixes[i], StringComparison.OrdinalIgnoreCase))
955 string strToParse = mgComponents[i]
956 .Remove(expectedPrefixes[i], StringComparison.OrdinalIgnoreCase)
959 altParseFailed |= !
int.TryParse(strToParse, out val);
960 components[i] = ((float) val) / 255f;
964 altParseFailed =
true;
973 if (errorMessages) { DebugConsole.ThrowError(
"Failed to parse the string \"" + stringColor +
"\" to Color"); }
979 for (
int i = 0; i < 4 && i < strComponents.Length; i++)
981 float.TryParse(strComponents[i], NumberStyles.Float, CultureInfo.InvariantCulture, out components[i]);
984 if (components.Any(c => c > 1.0f))
986 for (
int i = 0; i < 4; i++)
988 components[i] = components[i] / 255.0f;
991 if (strComponents.Length < 4) components[3] = 1.0f;
995 return new Color(components[0], components[1], components[2], components[3]);
998 public static Rectangle ParseRect(
string stringRect,
bool requireSize,
bool errorMessages =
true)
1000 string[] strComponents = stringRect.Split(
',');
1001 if ((strComponents.Length < 3 && requireSize) || strComponents.Length < 2)
1003 if (errorMessages) { DebugConsole.ThrowError(
"Failed to parse the string \"" + stringRect +
"\" to Rectangle"); }
1007 int[] components =
new int[4] { 0, 0, 0, 0 };
1008 for (
int i = 0; i < 4 && i < strComponents.Length; i++)
1010 int.TryParse(strComponents[i], out components[i]);
1013 return new Rectangle(components[0], components[1], components[2], components[3]);
1016 public static float[] ParseFloatArray(
string[] stringArray)
1018 if (stringArray ==
null || stringArray.Length == 0)
return null;
1020 float[] floatArray =
new float[stringArray.Length];
1021 for (
int i = 0; i < floatArray.Length; i++)
1023 floatArray[i] = 0.0f;
1024 Single.TryParse(stringArray[i], NumberStyles.Float, CultureInfo.InvariantCulture, out floatArray[i]);
1031 public static Range<int> ParseRange(
string rangeString)
1033 if (
string.IsNullOrWhiteSpace(rangeString)) {
return GetDefault(rangeString); }
1035 string[] split = rangeString.Split(
'-');
1036 return split.Length
switch
1038 1 when TryParseInt(split[0], out
int value) =>
new Range<int>(value, value),
1039 2 when TryParseInt(split[0], out
int min) && TryParseInt(split[1], out
int max) && min < max =>
new Range<int>(min, max),
1040 _ => GetDefault(rangeString)
1043 static bool TryParseInt(
string value, out
int result)
1045 if (!
string.IsNullOrWhiteSpace(value))
1047 return int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
1054 static Range<int> GetDefault(
string rangeString)
1056 DebugConsole.ThrowError($
"Error parsing range: \"{rangeString}\" (using default value 0-99)");
1057 return new Range<int>(0, 99);
1061 public static Identifier VariantOf(
this XElement element) =>
1062 element.GetAttributeIdentifier(
"inherit", element.GetAttributeIdentifier(
"variantof",
""));
1064 public static bool IsOverride(
this XElement element) => element.NameAsIdentifier() ==
"override";
1070 public static XElement GetRootExcludingOverride(
this XDocument doc) => doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
1072 public static XElement FirstElement(
this XElement element) => element.Elements().FirstOrDefault();
1074 public static XAttribute GetAttribute(
this XElement element,
string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => element.GetAttribute(a => a.Name.ToString().Equals(name, comparisonMethod));
1076 public static bool TrySetAttributeValue(
this XElement element,
string name,
object value, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase)
1078 var attribute = GetAttribute(element, name, comparisonMethod);
1079 if (attribute ==
null) {
return false; }
1080 attribute.SetValue(value);
1084 public static void SetAttribute(
this XElement element,
string name,
object value)
1086 if (!TrySetAttributeValue(element, name, value))
1088 element.SetAttributeValue(name, value);
1092 public static XAttribute GetAttribute(
this XElement element, Identifier name) => element.GetAttribute(name.Value, StringComparison.OrdinalIgnoreCase);
1094 public static XAttribute GetAttribute(
this XElement element, Func<XAttribute, bool> predicate) => element.Attributes().FirstOrDefault(predicate);
1099 public static XElement GetChildElement(
this XContainer container,
string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => container.Elements().FirstOrDefault(e => e.Name.ToString().Equals(name, comparisonMethod));
1104 public static IEnumerable<XElement> GetChildElements(
this XContainer container,
string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => container.Elements().Where(e => e.Name.ToString().Equals(name, comparisonMethod));
1106 public static IEnumerable<XElement> GetChildElements(
this XContainer container, params
string[] names)
1108 return names.SelectMany(name => container.GetChildElements(name));
1111 public static bool ComesAfter(
this XElement element, XElement other)
1113 if (element.Parent != other.Parent) {
return false; }
1114 foreach (var child
in element.Parent.Elements())
1116 if (child == element) {
return false; }
1117 if (child == other) {
return true; }
1122 public static Identifier NameAsIdentifier(
this XElement elem)
1124 return elem.Name.LocalName.ToIdentifier();
1127 public static Identifier NameAsIdentifier(
this XAttribute attr)
1129 return attr.Name.LocalName.ToIdentifier();