1 using Microsoft.Xna.Framework;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.Diagnostics;
6 using System.Globalization;
8 using System.Reflection;
9 using System.Runtime.CompilerServices;
12 using System.Xml.Linq;
20 public static class XMLExtensions
22 private readonly
static ImmutableDictionary<Type, Func<string, object, object>> Converters
23 =
new Dictionary<Type, Func<string, object, object>>()
25 { typeof(
string), (str, defVal) => str },
26 { typeof(
int), (str, defVal) =>
int.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out
int result) ? result : defVal },
27 { typeof(uint), (str, defVal) => uint.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out uint result) ? result : defVal },
28 { typeof(UInt64), (str, defVal) => UInt64.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out UInt64 result) ? result : defVal },
29 { typeof(
float), (str, defVal) =>
float.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out
float result) ? result : defVal },
30 { typeof(
bool), (str, defVal) =>
bool.TryParse(str, out
bool result) ? result : defVal },
31 { typeof(Color), (str, defVal) => ParseColor(str) },
32 { typeof(Vector2), (str, defVal) => ParseVector2(str) },
33 { typeof(Vector3), (str, defVal) => ParseVector3(str) },
34 { typeof(Vector4), (str, defVal) => ParseVector4(str) },
35 { typeof(Rectangle), (str, defVal) => ParseRect(str,
true) }
36 }.ToImmutableDictionary();
38 public static string ParseContentPathFromUri(
this XObject element)
39 => !
string.IsNullOrWhiteSpace(element.BaseUri)
40 ? System.
IO.
Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri.CleanUpPath())
43 public static readonly XmlReaderSettings ReaderSettings =
new XmlReaderSettings
45 DtdProcessing = DtdProcessing.Prohibit,
47 IgnoreWhitespace =
true,
50 public static XmlReader CreateReader(System.IO.Stream stream,
string baseUri =
"")
51 => XmlReader.Create(stream, ReaderSettings, baseUri);
53 public static XDocument TryLoadXml(System.IO.Stream stream)
58 using XmlReader reader = CreateReader(stream);
59 doc = XDocument.Load(reader);
63 DebugConsole.ThrowError($
"Couldn't load xml document from stream!", e);
66 if (doc?.Root ==
null)
68 DebugConsole.ThrowError(
"XML could not be loaded from stream: Document or the root element is invalid!");
74 public static XDocument TryLoadXml(ContentPath path) => TryLoadXml(path.Value);
76 public static XDocument TryLoadXml(
string filePath)
78 var doc = TryLoadXml(filePath, out var exception);
79 if (exception !=
null)
81 DebugConsole.ThrowError($
"Couldn't load xml document \"{filePath}\"!", exception);
85 DebugConsole.ThrowError($
"File \"{filePath}\" could not be loaded: Document or the root element is invalid!");
90 public static XDocument TryLoadXml(
string filePath, out Exception exception)
96 ToolBox.IsProperFilenameCase(filePath);
97 using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
98 using XmlReader reader = CreateReader(stream, Path.GetFullPath(filePath));
99 doc = XDocument.Load(reader, LoadOptions.SetBaseUri);
106 if (doc?.Root ==
null)
113 public static object GetAttributeObject(XAttribute attribute)
115 if (attribute ==
null) {
return null; }
117 return ParseToObject(attribute.Value.ToString());
120 public static object ParseToObject(
string value)
122 if (value.Contains(
".") && Single.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out
float floatVal))
126 if (Int32.TryParse(value, out
int intVal))
131 string lowerTrimmedVal = value.ToLowerInvariant().Trim();
132 if (lowerTrimmedVal ==
"true")
136 if (lowerTrimmedVal ==
"false")
145 public static string GetAttributeString(
this XElement element,
string name,
string defaultValue)
147 var attribute = element?.GetAttribute(name);
148 if (attribute ==
null) {
return defaultValue; }
149 string str = GetAttributeString(attribute, defaultValue);
151 if (!str.IsNullOrEmpty() &&
152 (str.Contains(
"%ModDir", StringComparison.OrdinalIgnoreCase)
153 || str.CleanUpPathCrossPlatform(correctFilenameCase:
false).StartsWith(
"Content/", StringComparison.OrdinalIgnoreCase)))
155 DebugConsole.ThrowError($
"Use {nameof(GetAttributeContentPath)} instead of {nameof(GetAttributeString)}\n{Environment.StackTrace.CleanupStackTrace()}");
156 if (Debugger.IsAttached) { Debugger.Break(); }
162 public static string GetAttributeStringUnrestricted(
this XElement element,
string name,
string defaultValue)
164 #warning TODO: remove?
165 var attribute = element?.GetAttribute(name);
166 if (attribute ==
null) {
return defaultValue; }
167 return GetAttributeString(attribute, defaultValue);
170 public static bool DoesAttributeReferenceFileNameAlone(
this XElement element,
string name)
172 string texName = element.GetAttributeStringUnrestricted(name,
"");
173 return !texName.IsNullOrEmpty() & !texName.Contains(
"/") && !texName.Contains(
"%ModDir", StringComparison.OrdinalIgnoreCase);
176 public static ContentPath GetAttributeContentPath(
this XElement element,
string name, ContentPackage contentPackage)
178 var attribute = element?.GetAttribute(name);
179 if (attribute ==
null) {
return null; }
180 return ContentPath.FromRaw(contentPackage, GetAttributeString(attribute,
null));
183 public static Identifier GetAttributeIdentifier(
this XElement element,
string name,
string defaultValue)
185 return element.GetAttributeString(name, defaultValue).ToIdentifier();
188 public static Identifier GetAttributeIdentifier(
this XElement element,
string name, Identifier defaultValue)
190 return element.GetAttributeIdentifier(name, defaultValue.Value);
193 private static string GetAttributeString(XAttribute attribute,
string defaultValue)
195 string value = attribute.Value;
196 return string.IsNullOrEmpty(value) ? defaultValue : value;
199 public static string[] GetAttributeStringArray(
this XElement element,
string name,
string[] defaultValue,
bool trim =
true,
bool convertToLowerInvariant =
false)
201 var attribute = element?.GetAttribute(name);
202 if (attribute ==
null) {
return defaultValue; }
204 string stringValue = attribute.Value;
205 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
207 string[] splitValue = stringValue.Split(
',',
',');
209 for (
int i = 0; i < splitValue.Length; i++)
211 if (convertToLowerInvariant) { splitValue[i] = splitValue[i].ToLowerInvariant(); }
212 if (trim) { splitValue[i] = splitValue[i].Trim(); }
218 public static Identifier[] GetAttributeIdentifierArray(
this XElement element, Identifier[] defaultValue, params
string[] matchingAttributeName)
220 if (element ==
null) {
return defaultValue; }
221 foreach (
string name
in matchingAttributeName)
223 var value = element.GetAttributeIdentifierArray(name, defaultValue);
224 if (value != defaultValue) {
return value; }
229 public static Identifier[] GetAttributeIdentifierArray(
this XElement element,
string name, Identifier[] defaultValue,
bool trim =
true)
231 return element.GetAttributeStringArray(name,
null, trim: trim, convertToLowerInvariant:
false)
236 public static ImmutableHashSet<Identifier> GetAttributeIdentifierImmutableHashSet(
this XElement element,
string key, ImmutableHashSet<Identifier> defaultValue,
bool trim =
true)
238 return element.GetAttributeIdentifierArray(key,
null, trim)?.ToImmutableHashSet()
242 public static float GetAttributeFloat(
this XElement element,
float defaultValue, params
string[] matchingAttributeName)
244 if (element ==
null) {
return defaultValue; }
246 foreach (
string name
in matchingAttributeName)
248 var attribute = element.GetAttribute(name);
249 if (attribute ==
null) {
continue; }
250 return GetAttributeFloat(attribute, defaultValue);
256 public static float GetAttributeFloat(
this XElement element,
string name,
float defaultValue) => GetAttributeFloat(element?.GetAttribute(name), defaultValue);
258 public static float GetAttributeFloat(
this XAttribute attribute,
float defaultValue)
260 if (attribute ==
null) {
return defaultValue; }
262 float val = defaultValue;
266 string strVal = attribute.Value;
267 if (strVal.LastOrDefault() ==
'f')
269 strVal = strVal.Substring(0, strVal.Length - 1);
271 val =
float.Parse(strVal, CultureInfo.InvariantCulture);
275 DebugConsole.ThrowError(
"Error in " + attribute +
"! ", e);
281 public static double GetAttributeDouble(
this XElement element,
string name,
double defaultValue) => GetAttributeDouble(element?.GetAttribute(name), defaultValue);
283 public static double GetAttributeDouble(
this XAttribute attribute,
double defaultValue)
285 if (attribute ==
null) {
return defaultValue; }
287 double val = defaultValue;
290 string strVal = attribute.Value;
291 if (strVal.LastOrDefault() ==
'f')
293 strVal = strVal.Substring(0, strVal.Length - 1);
295 val =
double.Parse(strVal, CultureInfo.InvariantCulture);
299 DebugConsole.ThrowError(
"Error in " + attribute +
"!", e);
306 public static float[] GetAttributeFloatArray(
this XElement element,
string name,
float[] defaultValue)
308 var attribute = element?.GetAttribute(name);
309 if (attribute ==
null) {
return defaultValue; }
311 string stringValue = attribute.Value;
312 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
314 string[] splitValue = stringValue.Split(
',');
315 float[] floatValue =
new float[splitValue.Length];
316 for (
int i = 0; i < splitValue.Length; i++)
320 string strVal = splitValue[i];
321 if (strVal.LastOrDefault() ==
'f')
323 strVal = strVal.Substring(0, strVal.Length - 1);
325 floatValue[i] =
float.Parse(strVal, CultureInfo.InvariantCulture);
329 LogAttributeError(attribute, element, e);
336 public static bool TryGetAttributeInt(
this XElement element,
string name, out
int result)
338 var attribute = element?.GetAttribute(name);
340 if (attribute ==
null) {
return false; }
342 if (
int.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var intVal))
347 if (
float.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var floatVal))
349 result = (int)floatVal;
355 public static int GetAttributeInt(
this XElement element,
string name,
int defaultValue) => GetAttributeInt(element?.GetAttribute(name), defaultValue);
357 public static int GetAttributeInt(
this XAttribute attribute,
int defaultValue)
359 if (attribute ==
null) {
return defaultValue; }
361 int val = defaultValue;
365 if (!Int32.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out val))
367 val = (int)
float.Parse(attribute.Value, CultureInfo.InvariantCulture);
372 LogAttributeError(attribute, attribute.Parent, e);
378 public static uint GetAttributeUInt(
this XElement element,
string name, uint defaultValue)
380 var attribute = element?.GetAttribute(name);
381 if (attribute ==
null) {
return defaultValue; }
383 uint val = defaultValue;
387 val = UInt32.Parse(attribute.Value);
391 LogAttributeError(attribute, element, e);
397 public static ushort GetAttributeUInt16(
this XElement element,
string name, ushort defaultValue)
399 var attribute = element?.GetAttribute(name);
400 if (attribute ==
null) {
return defaultValue; }
402 ushort val = defaultValue;
406 val = ushort.Parse(attribute.Value);
410 LogAttributeError(attribute, element, e);
416 public static UInt64 GetAttributeUInt64(
this XElement element,
string name, UInt64 defaultValue)
418 var attribute = element?.GetAttribute(name);
419 if (attribute ==
null) {
return defaultValue; }
421 UInt64 val = defaultValue;
425 val = UInt64.Parse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture);
429 LogAttributeError(attribute, element, e);
435 public static Option<SerializableDateTime> GetAttributeDateTime(
436 this XElement element,
string name)
438 var attribute = element?.GetAttribute(name);
439 if (attribute ==
null) {
return Option<SerializableDateTime>.None(); }
441 string attrVal = attribute.Value;
442 return SerializableDateTime.Parse(attrVal);
445 public static Version GetAttributeVersion(
this XElement element,
string name, Version defaultValue)
447 var attribute = element?.GetAttribute(name);
448 if (attribute ==
null) {
return defaultValue; }
450 Version val = defaultValue;
454 val = Version.Parse(attribute.Value);
458 LogAttributeError(attribute, element, e);
464 public static int[] GetAttributeIntArray(
this XElement element,
string name,
int[] defaultValue)
466 var attribute = element?.GetAttribute(name);
467 if (attribute ==
null) {
return defaultValue; }
469 string stringValue = attribute.Value;
470 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
472 string[] splitValue = stringValue.Split(
',');
473 int[] intValue =
new int[splitValue.Length];
474 for (
int i = 0; i < splitValue.Length; i++)
478 int val = Int32.Parse(splitValue[i]);
483 LogAttributeError(attribute, element, e);
489 public static ushort[] GetAttributeUshortArray(
this XElement element,
string name, ushort[] defaultValue)
491 var attribute = element?.GetAttribute(name);
492 if (attribute ==
null) {
return defaultValue; }
494 string stringValue = attribute.Value;
495 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
497 string[] splitValue = stringValue.Split(
',');
498 ushort[] ushortValue =
new ushort[splitValue.Length];
499 for (
int i = 0; i < splitValue.Length; i++)
503 ushort val = ushort.Parse(splitValue[i]);
504 ushortValue[i] = val;
508 LogAttributeError(attribute, element, e);
515 public static T GetAttributeEnum<T>(
this XElement element,
string name, T defaultValue) where T :
struct, Enum
517 var attr = element?.GetAttribute(name);
518 if (attr ==
null) {
return defaultValue; }
520 if (Enum.TryParse(attr.Value,
true, out T result))
524 else if (
int.TryParse(attr.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out
int resultInt))
526 return Unsafe.As<int, T>(ref resultInt);
528 DebugConsole.ThrowError($
"Error in {attr}! \"{attr}\" is not a valid {typeof(T).Name} value");
533 public static bool GetAttributeBool(
this XElement element,
string name,
bool defaultValue)
535 var attribute = element?.GetAttribute(name);
536 if (attribute ==
null) {
return defaultValue; }
537 return attribute.GetAttributeBool(defaultValue);
540 public static bool GetAttributeBool(
this XAttribute attribute,
bool defaultValue)
542 if (attribute ==
null) {
return defaultValue; }
544 string val = attribute.Value.ToLowerInvariant().Trim();
554 DebugConsole.ThrowError(
"Error in " + attribute.Value.ToString() +
"! \"" + val +
"\" is not a valid boolean value");
558 public static Point GetAttributePoint(
this XElement element,
string name, Point defaultValue)
560 var attribute = element?.GetAttribute(name);
561 if (attribute ==
null) {
return defaultValue; }
562 return ParsePoint(attribute.Value);
565 public static Vector2 GetAttributeVector2(
this XElement element,
string name, Vector2 defaultValue)
567 var attribute = element?.GetAttribute(name);
568 if (attribute ==
null) {
return defaultValue; }
569 return ParseVector2(attribute.Value);
572 public static Vector3 GetAttributeVector3(
this XElement element,
string name, Vector3 defaultValue)
574 var attribute = element?.GetAttribute(name);
575 if (attribute ==
null) {
return defaultValue; }
576 return ParseVector3(attribute.Value);
579 public static Vector4 GetAttributeVector4(
this XElement element,
string name, Vector4 defaultValue)
581 var attribute = element?.GetAttribute(name);
582 if (attribute ==
null) {
return defaultValue; }
583 return ParseVector4(attribute.Value);
586 public static Color GetAttributeColor(
this XElement element,
string name, Color defaultValue)
588 var attribute = element?.GetAttribute(name);
589 if (attribute ==
null) {
return defaultValue; }
590 return ParseColor(attribute.Value);
593 public static Color? GetAttributeColor(
this XElement element,
string name)
595 var attribute = element?.GetAttribute(name);
596 if (attribute ==
null) {
return null; }
597 return ParseColor(attribute.Value);
600 public static Color[] GetAttributeColorArray(
this XElement element,
string name, Color[] defaultValue)
602 var attribute = element?.GetAttribute(name);
603 if (attribute ==
null) {
return defaultValue; }
605 string stringValue = attribute.Value;
606 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
608 string[] splitValue = stringValue.Split(
';');
609 Color[] colorValue =
new Color[splitValue.Length];
610 for (
int i = 0; i < splitValue.Length; i++)
614 Color val = ParseColor(splitValue[i],
true);
619 LogAttributeError(attribute, element, e);
626 private static void LogAttributeError(XAttribute attribute, XElement element, Exception e)
628 string elementStr = element.ToString();
629 if (elementStr.Length > 500)
631 DebugConsole.ThrowError($
"Error when reading attribute \"{attribute}\"!", e);
635 DebugConsole.ThrowError($
"Error when reading attribute \"{attribute.Name}\" from {elementStr}!", e);
640 public static KeyOrMouse GetAttributeKeyOrMouse(
this XElement element,
string name, KeyOrMouse defaultValue)
642 string strValue = element.GetAttributeString(name, defaultValue?.ToString() ??
"");
643 if (Enum.TryParse(strValue,
true, out Microsoft.Xna.Framework.Input.Keys key))
647 else if (Enum.TryParse(strValue, out
MouseButton mouseButton))
651 else if (
int.TryParse(strValue, NumberStyles.Any, CultureInfo.InvariantCulture, out
int mouseButtonInt) &&
656 else if (
string.Equals(strValue,
"LeftMouse", StringComparison.OrdinalIgnoreCase))
660 else if (
string.Equals(strValue,
"RightMouse", StringComparison.OrdinalIgnoreCase))
668 public static Rectangle GetAttributeRect(
this XElement element,
string name, Rectangle defaultValue)
670 var attribute = element?.GetAttribute(name);
671 if (attribute ==
null) {
return defaultValue; }
672 return ParseRect(attribute.Value,
false);
676 public static (T1, T2) GetAttributeTuple<T1, T2>(
this XElement element,
string name, (T1, T2) defaultValue)
678 string strValue = element.GetAttributeString(name, $
"({defaultValue.Item1}, {defaultValue.Item2})").Trim();
680 return ParseTuple(strValue, defaultValue);
683 public static (T1, T2)[] GetAttributeTupleArray<T1, T2>(
this XElement element,
string name,
684 (T1, T2)[] defaultValue)
686 var attribute = element?.GetAttribute(name);
687 if (attribute ==
null) {
return defaultValue; }
689 string stringValue = attribute.Value;
690 if (
string.IsNullOrEmpty(stringValue)) {
return defaultValue; }
692 return stringValue.Split(
';').Select(s => ParseTuple<T1, T2>(s,
default)).ToArray();
695 public static Range<int> GetAttributeRange(
this XElement element,
string name, Range<int> defaultValue)
697 var attribute = element?.GetAttribute(name);
698 if (attribute is
null) {
return defaultValue; }
700 string stringValue = attribute.Value;
701 return string.IsNullOrEmpty(stringValue) ? defaultValue : ParseRange(stringValue);
704 public static string ElementInnerText(
this XElement el)
706 StringBuilder str =
new StringBuilder();
707 foreach (XNode element
in el.DescendantNodes().Where(x => x.NodeType == XmlNodeType.Text))
709 str.Append(element.ToString());
711 return str.ToString();
714 public static string PointToString(Point point)
716 return point.X.ToString() +
"," + point.Y.ToString();
719 public static string Vector2ToString(Vector2 vector)
721 return vector.X.ToString(
"G", CultureInfo.InvariantCulture) +
"," + vector.Y.ToString(
"G", CultureInfo.InvariantCulture);
724 public static string Vector3ToString(Vector3 vector,
string format =
"G")
726 return vector.X.ToString(format, CultureInfo.InvariantCulture) +
"," +
727 vector.Y.ToString(format, CultureInfo.InvariantCulture) +
"," +
728 vector.Z.ToString(format, CultureInfo.InvariantCulture);
731 public static string Vector4ToString(Vector4 vector,
string format =
"G")
733 return vector.X.ToString(format, CultureInfo.InvariantCulture) +
"," +
734 vector.Y.ToString(format, CultureInfo.InvariantCulture) +
"," +
735 vector.Z.ToString(format, CultureInfo.InvariantCulture) +
"," +
736 vector.W.ToString(format, CultureInfo.InvariantCulture);
739 [Obsolete(
"Prefer XMLExtensions.ToStringHex")]
740 public static string ColorToString(Color color)
741 => $
"{color.R},{color.G},{color.B},{color.A}";
743 public static string ToStringHex(
this Color color)
744 => $
"#{color.R:X2}{color.G:X2}{color.B:X2}"
745 + ((color.A < 255) ? $
"{color.A:X2}" :
"");
747 public static string RectToString(Rectangle rect)
749 return rect.X +
"," + rect.Y +
"," + rect.Width +
"," + rect.Height;
752 public static (T1, T2) ParseTuple<T1, T2>(
string strValue, (T1, T2) defaultValue)
754 strValue = strValue.Trim();
756 if (strValue[0] !=
'(' || strValue[^1] !=
')') {
return defaultValue; }
758 strValue = strValue[1..^1];
760 string[] elems = strValue.Split(
',');
761 if (elems.Length != 2) {
return defaultValue; }
763 return ((T1)Converters[typeof(T1)].Invoke(elems[0], defaultValue.Item1),
764 (T2)Converters[typeof(T2)].Invoke(elems[1], defaultValue.Item2));
767 public static Point ParsePoint(
string stringPoint,
bool errorMessages =
true)
769 string[] components = stringPoint.Split(
',');
770 Point point = Point.Zero;
772 if (components.Length != 2)
774 if (!errorMessages) {
return point; }
775 DebugConsole.ThrowError(
"Failed to parse the string \"" + stringPoint +
"\" to Vector2");
779 int.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out point.X);
780 int.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out point.Y);
784 public static Vector2 ParseVector2(
string stringVector2,
bool errorMessages =
true)
786 string[] components = stringVector2.Split(
',');
788 Vector2 vector = Vector2.Zero;
790 if (components.Length != 2)
792 if (!errorMessages) {
return vector; }
793 DebugConsole.ThrowError(
"Failed to parse the string \"" + stringVector2 +
"\" to Vector2");
797 float.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.X);
798 float.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Y);
803 public static Vector3 ParseVector3(
string stringVector3,
bool errorMessages =
true)
805 string[] components = stringVector3.Split(
',');
807 Vector3 vector = Vector3.Zero;
809 if (components.Length != 3)
811 if (!errorMessages) {
return vector; }
812 DebugConsole.ThrowError(
"Failed to parse the string \"" + stringVector3 +
"\" to Vector3");
816 Single.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.X);
817 Single.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Y);
818 Single.TryParse(components[2], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Z);
823 public static Vector4 ParseVector4(
string stringVector4,
bool errorMessages =
true)
825 string[] components = stringVector4.Split(
',');
827 Vector4 vector = Vector4.Zero;
829 if (components.Length < 3)
831 if (errorMessages) { DebugConsole.ThrowError(
"Failed to parse the string \"" + stringVector4 +
"\" to Vector4"); }
835 Single.TryParse(components[0], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.X);
836 Single.TryParse(components[1], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.Y);
837 Single.TryParse(components[2], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.Z);
838 if (components.Length > 3)
840 Single.TryParse(components[3], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.W);
846 private static readonly ImmutableDictionary<Identifier, Color> monoGameColors =
848 .GetProperties(BindingFlags.Static | BindingFlags.Public)
849 .Where(p => p.PropertyType == typeof(Color))
850 .Select(p => (p.Name.ToIdentifier(), p.GetValueFromStaticProperty<Color>()))
851 .ToImmutableDictionary();
853 public static Color ParseColor(
string stringColor,
bool errorMessages =
true)
855 if (stringColor.StartsWith(
"gui.", StringComparison.OrdinalIgnoreCase))
858 Identifier colorName = stringColor.Substring(4).ToIdentifier();
859 if (GUIStyle.Colors.TryGetValue(colorName, out GUIColor guiColor))
861 return guiColor.Value;
866 if (stringColor.StartsWith(
"faction.", StringComparison.OrdinalIgnoreCase))
868 Identifier factionId = stringColor.Substring(8).ToIdentifier();
869 if (FactionPrefab.Prefabs.TryGet(factionId, out var faction))
871 return faction.IconColor;
876 if (monoGameColors.TryGetValue(stringColor.ToIdentifier(), out var monoGameColor))
878 return monoGameColor;
881 string[] strComponents = stringColor.Split(
',');
883 Color color = Color.White;
885 float[] components =
new float[4] { 1.0f, 1.0f, 1.0f, 1.0f };
887 if (strComponents.Length == 1)
889 bool altParseFailed =
true;
890 stringColor = stringColor.Trim();
891 if (stringColor.Length > 0 && stringColor[0] ==
'#')
893 stringColor = stringColor.Substring(1);
895 if (
int.TryParse(stringColor, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out
int colorInt))
897 if (stringColor.Length == 6)
899 colorInt = (colorInt << 8) | 0xff;
901 components[0] = ((float)((colorInt & 0xff000000) >> 24)) / 255.0f;
902 components[1] = ((float)((colorInt & 0x00ff0000) >> 16)) / 255.0f;
903 components[2] = ((float)((colorInt & 0x0000ff00) >> 8)) / 255.0f;
904 components[3] = ((float)(colorInt & 0x000000ff)) / 255.0f;
906 altParseFailed =
false;
909 else if (stringColor.Length > 0 && stringColor[0] ==
'{')
911 stringColor = stringColor.Substring(1, stringColor.Length-2);
913 string[] mgComponents = stringColor.Split(
' ');
914 if (mgComponents.Length == 4)
916 altParseFailed =
false;
918 string[] expectedPrefixes = {
"R:",
"G:",
"B:",
"A:"};
919 for (
int i = 0; i < 4; i++)
921 if (mgComponents[i].StartsWith(expectedPrefixes[i], StringComparison.OrdinalIgnoreCase))
923 string strToParse = mgComponents[i]
924 .Remove(expectedPrefixes[i], StringComparison.OrdinalIgnoreCase)
927 altParseFailed |= !
int.TryParse(strToParse, out val);
928 components[i] = ((float) val) / 255f;
932 altParseFailed =
true;
941 if (errorMessages) { DebugConsole.ThrowError(
"Failed to parse the string \"" + stringColor +
"\" to Color"); }
947 for (
int i = 0; i < 4 && i < strComponents.Length; i++)
949 float.TryParse(strComponents[i], NumberStyles.Float, CultureInfo.InvariantCulture, out components[i]);
952 if (components.Any(c => c > 1.0f))
954 for (
int i = 0; i < 4; i++)
956 components[i] = components[i] / 255.0f;
959 if (strComponents.Length < 4) components[3] = 1.0f;
963 return new Color(components[0], components[1], components[2], components[3]);
966 public static Rectangle ParseRect(
string stringRect,
bool requireSize,
bool errorMessages =
true)
968 string[] strComponents = stringRect.Split(
',');
969 if ((strComponents.Length < 3 && requireSize) || strComponents.Length < 2)
971 if (errorMessages) { DebugConsole.ThrowError(
"Failed to parse the string \"" + stringRect +
"\" to Rectangle"); }
975 int[] components =
new int[4] { 0, 0, 0, 0 };
976 for (
int i = 0; i < 4 && i < strComponents.Length; i++)
978 int.TryParse(strComponents[i], out components[i]);
981 return new Rectangle(components[0], components[1], components[2], components[3]);
984 public static float[] ParseFloatArray(
string[] stringArray)
986 if (stringArray ==
null || stringArray.Length == 0)
return null;
988 float[] floatArray =
new float[stringArray.Length];
989 for (
int i = 0; i < floatArray.Length; i++)
991 floatArray[i] = 0.0f;
992 Single.TryParse(stringArray[i], NumberStyles.Float, CultureInfo.InvariantCulture, out floatArray[i]);
999 public static Range<int> ParseRange(
string rangeString)
1001 if (
string.IsNullOrWhiteSpace(rangeString)) {
return GetDefault(rangeString); }
1003 string[] split = rangeString.Split(
'-');
1004 return split.Length
switch
1006 1 when TryParseInt(split[0], out
int value) =>
new Range<int>(value, value),
1007 2 when TryParseInt(split[0], out
int min) && TryParseInt(split[1], out
int max) && min < max =>
new Range<int>(min, max),
1008 _ => GetDefault(rangeString)
1011 static bool TryParseInt(
string value, out
int result)
1013 if (!
string.IsNullOrWhiteSpace(value))
1015 return int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
1022 static Range<int> GetDefault(
string rangeString)
1024 DebugConsole.ThrowError($
"Error parsing range: \"{rangeString}\" (using default value 0-99)");
1025 return new Range<int>(0, 99);
1029 public static Identifier VariantOf(
this XElement element) =>
1030 element.GetAttributeIdentifier(
"inherit", element.GetAttributeIdentifier(
"variantof",
""));
1032 public static string[] ParseStringArray(
string stringArrayValues)
1034 return string.IsNullOrEmpty(stringArrayValues) ? Array.Empty<
string>() : stringArrayValues.Split(
';');
1037 public static Identifier[] ParseIdentifierArray(
string stringArrayValues)
1039 return ParseStringArray(stringArrayValues).ToIdentifiers().ToArray();
1042 public static bool IsOverride(
this XElement element) => element.NameAsIdentifier() ==
"override";
1048 public static XElement GetRootExcludingOverride(
this XDocument doc) => doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
1050 public static XElement FirstElement(
this XElement element) => element.Elements().FirstOrDefault();
1052 public static XAttribute GetAttribute(
this XElement element,
string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => element.GetAttribute(a => a.Name.ToString().Equals(name, comparisonMethod));
1054 public static void SetAttributeValue(
this XElement element,
string name,
object value, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => GetAttribute(element, name, comparisonMethod)?.SetValue(value);
1056 public static XAttribute GetAttribute(
this XElement element, Identifier name) => element.GetAttribute(name.Value, StringComparison.OrdinalIgnoreCase);
1058 public static XAttribute GetAttribute(
this XElement element, Func<XAttribute, bool> predicate) => element.Attributes().FirstOrDefault(predicate);
1063 public static XElement GetChildElement(
this XContainer container,
string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => container.Elements().FirstOrDefault(e => e.Name.ToString().Equals(name, comparisonMethod));
1068 public static IEnumerable<XElement> GetChildElements(
this XContainer container,
string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => container.Elements().Where(e => e.Name.ToString().Equals(name, comparisonMethod));
1070 public static IEnumerable<XElement> GetChildElements(
this XContainer container, params
string[] names)
1072 return names.SelectMany(name => container.GetChildElements(name));
1075 public static bool ComesAfter(
this XElement element, XElement other)
1077 if (element.Parent != other.Parent) {
return false; }
1078 foreach (var child
in element.Parent.Elements())
1080 if (child == element) {
return false; }
1081 if (child == other) {
return true; }
1086 public static Identifier NameAsIdentifier(
this XElement elem)
1088 return elem.Name.LocalName.ToIdentifier();
1091 public static Identifier NameAsIdentifier(
this XAttribute attr)
1093 return attr.Name.LocalName.ToIdentifier();