Client LuaCsForBarotrauma
XMLExtensions.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.Diagnostics;
6 using System.Globalization;
7 using System.Linq;
8 using System.Reflection;
9 using System.Runtime.CompilerServices;
10 using System.Text;
11 using System.Xml;
12 using System.Xml.Linq;
14 using File = Barotrauma.IO.File;
15 using FileStream = Barotrauma.IO.FileStream;
16 using Path = Barotrauma.IO.Path;
17 
18 namespace Barotrauma
19 {
20  public static class XMLExtensions
21  {
22  private readonly static ImmutableDictionary<Type, Func<string, object, object>> Converters
23  = new Dictionary<Type, Func<string, object, object>>()
24  {
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();
37 
38  public static string ParseContentPathFromUri(this XObject element)
39  => !string.IsNullOrWhiteSpace(element.BaseUri)
40  ? System.IO.Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri.CleanUpPath())
41  : "";
42 
43  public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings
44  {
45  DtdProcessing = DtdProcessing.Prohibit,
46  XmlResolver = null,
47  IgnoreWhitespace = true,
48  };
49 
50  public static XmlReader CreateReader(System.IO.Stream stream, string baseUri = "")
51  => XmlReader.Create(stream, ReaderSettings, baseUri);
52 
53  public static XDocument TryLoadXml(System.IO.Stream stream)
54  {
55  XDocument doc;
56  try
57  {
58  using XmlReader reader = CreateReader(stream);
59  doc = XDocument.Load(reader);
60  }
61  catch (Exception e)
62  {
63  DebugConsole.ThrowError($"Couldn't load xml document from stream!", e);
64  return null;
65  }
66  if (doc?.Root == null)
67  {
68  DebugConsole.ThrowError("XML could not be loaded from stream: Document or the root element is invalid!");
69  return null;
70  }
71  return doc;
72  }
73 
74  public static XDocument TryLoadXml(ContentPath path) => TryLoadXml(path.Value);
75 
76  public static XDocument TryLoadXml(string filePath)
77  {
78  var doc = TryLoadXml(filePath, out var exception);
79  if (exception != null)
80  {
81  DebugConsole.ThrowError($"Couldn't load xml document \"{filePath}\"!", exception);
82  }
83  else if (doc is null)
84  {
85  DebugConsole.ThrowError($"File \"{filePath}\" could not be loaded: Document or the root element is invalid!");
86  }
87  return doc;
88  }
89 
90  public static XDocument TryLoadXml(string filePath, out Exception exception)
91  {
92  exception = null;
93  XDocument doc;
94  try
95  {
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);
100  }
101  catch (Exception e)
102  {
103  exception = e;
104  return null;
105  }
106  if (doc?.Root == null)
107  {
108  return null;
109  }
110  return doc;
111  }
112 
113  public static object GetAttributeObject(XAttribute attribute)
114  {
115  if (attribute == null) { return null; }
116 
117  return ParseToObject(attribute.Value.ToString());
118  }
119 
120  public static object ParseToObject(string value)
121  {
122  if (value.Contains(".") && Single.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float floatVal))
123  {
124  return floatVal;
125  }
126  if (Int32.TryParse(value, out int intVal))
127  {
128  return intVal;
129  }
130 
131  string lowerTrimmedVal = value.ToLowerInvariant().Trim();
132  if (lowerTrimmedVal == "true")
133  {
134  return true;
135  }
136  if (lowerTrimmedVal == "false")
137  {
138  return false;
139  }
140 
141  return value;
142  }
143 
144 
145  public static string GetAttributeString(this XElement element, string name, string defaultValue)
146  {
147  var attribute = element?.GetAttribute(name);
148  if (attribute == null) { return defaultValue; }
149  string str = GetAttributeString(attribute, defaultValue);
150 #if DEBUG
151  if (!str.IsNullOrEmpty() &&
152  (str.Contains("%ModDir", StringComparison.OrdinalIgnoreCase)
153  || str.CleanUpPathCrossPlatform(correctFilenameCase: false).StartsWith("Content/", StringComparison.OrdinalIgnoreCase)))
154  {
155  DebugConsole.ThrowError($"Use {nameof(GetAttributeContentPath)} instead of {nameof(GetAttributeString)}\n{Environment.StackTrace.CleanupStackTrace()}");
156  if (Debugger.IsAttached) { Debugger.Break(); }
157  }
158 #endif
159  return str;
160  }
161 
162  public static string GetAttributeStringUnrestricted(this XElement element, string name, string defaultValue)
163  {
164  #warning TODO: remove?
165  var attribute = element?.GetAttribute(name);
166  if (attribute == null) { return defaultValue; }
167  return GetAttributeString(attribute, defaultValue);
168  }
169 
170  public static bool DoesAttributeReferenceFileNameAlone(this XElement element, string name)
171  {
172  string texName = element.GetAttributeStringUnrestricted(name, "");
173  return !texName.IsNullOrEmpty() & !texName.Contains("/") && !texName.Contains("%ModDir", StringComparison.OrdinalIgnoreCase);
174  }
175 
176  public static ContentPath GetAttributeContentPath(this XElement element, string name, ContentPackage contentPackage)
177  {
178  var attribute = element?.GetAttribute(name);
179  if (attribute == null) { return null; }
180  return ContentPath.FromRaw(contentPackage, GetAttributeString(attribute, null));
181  }
182 
183  public static Identifier GetAttributeIdentifier(this XElement element, string name, string defaultValue)
184  {
185  return element.GetAttributeString(name, defaultValue).ToIdentifier();
186  }
187 
188  public static Identifier GetAttributeIdentifier(this XElement element, string name, Identifier defaultValue)
189  {
190  return element.GetAttributeIdentifier(name, defaultValue.Value);
191  }
192 
193  private static string GetAttributeString(XAttribute attribute, string defaultValue)
194  {
195  string value = attribute.Value;
196  return string.IsNullOrEmpty(value) ? defaultValue : value;
197  }
198 
199  public static string[] GetAttributeStringArray(this XElement element, string name, string[] defaultValue, bool trim = true, bool convertToLowerInvariant = false)
200  {
201  var attribute = element?.GetAttribute(name);
202  if (attribute == null) { return defaultValue; }
203 
204  string stringValue = attribute.Value;
205  if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
206 
207  string[] splitValue = stringValue.Split(',', ',');
208 
209  for (int i = 0; i < splitValue.Length; i++)
210  {
211  if (convertToLowerInvariant) { splitValue[i] = splitValue[i].ToLowerInvariant(); }
212  if (trim) { splitValue[i] = splitValue[i].Trim(); }
213  }
214 
215  return splitValue;
216  }
217 
218  public static Identifier[] GetAttributeIdentifierArray(this XElement element, Identifier[] defaultValue, params string[] matchingAttributeName)
219  {
220  if (element == null) { return defaultValue; }
221  foreach (string name in matchingAttributeName)
222  {
223  var value = element.GetAttributeIdentifierArray(name, defaultValue);
224  if (value != defaultValue) { return value; }
225  }
226  return defaultValue;
227  }
228 
229  public static Identifier[] GetAttributeIdentifierArray(this XElement element, string name, Identifier[] defaultValue, bool trim = true)
230  {
231  return element.GetAttributeStringArray(name, null, trim: trim, convertToLowerInvariant: false)
232  ?.ToIdentifiers()
233  ?? defaultValue;
234  }
235 
236  public static ImmutableHashSet<Identifier> GetAttributeIdentifierImmutableHashSet(this XElement element, string key, ImmutableHashSet<Identifier> defaultValue, bool trim = true)
237  {
238  return element.GetAttributeIdentifierArray(key, null, trim)?.ToImmutableHashSet()
239  ?? defaultValue;
240  }
241 
242  public static float GetAttributeFloat(this XElement element, float defaultValue, params string[] matchingAttributeName)
243  {
244  if (element == null) { return defaultValue; }
245 
246  foreach (string name in matchingAttributeName)
247  {
248  var attribute = element.GetAttribute(name);
249  if (attribute == null) { continue; }
250  return GetAttributeFloat(attribute, defaultValue);
251  }
252 
253  return defaultValue;
254  }
255 
256  public static float GetAttributeFloat(this XElement element, string name, float defaultValue) => GetAttributeFloat(element?.GetAttribute(name), defaultValue);
257 
258  public static float GetAttributeFloat(this XAttribute attribute, float defaultValue)
259  {
260  if (attribute == null) { return defaultValue; }
261 
262  float val = defaultValue;
263 
264  try
265  {
266  string strVal = attribute.Value;
267  if (strVal.LastOrDefault() == 'f')
268  {
269  strVal = strVal.Substring(0, strVal.Length - 1);
270  }
271  val = float.Parse(strVal, CultureInfo.InvariantCulture);
272  }
273  catch (Exception e)
274  {
275  DebugConsole.ThrowError("Error in " + attribute + "! ", e);
276  }
277 
278  return val;
279  }
280 
281  public static double GetAttributeDouble(this XElement element, string name, double defaultValue) => GetAttributeDouble(element?.GetAttribute(name), defaultValue);
282 
283  public static double GetAttributeDouble(this XAttribute attribute, double defaultValue)
284  {
285  if (attribute == null) { return defaultValue; }
286 
287  double val = defaultValue;
288  try
289  {
290  string strVal = attribute.Value;
291  if (strVal.LastOrDefault() == 'f')
292  {
293  strVal = strVal.Substring(0, strVal.Length - 1);
294  }
295  val = double.Parse(strVal, CultureInfo.InvariantCulture);
296  }
297  catch (Exception e)
298  {
299  DebugConsole.ThrowError("Error in " + attribute + "!", e);
300  }
301 
302  return val;
303  }
304 
305 
306  public static float[] GetAttributeFloatArray(this XElement element, string name, float[] defaultValue)
307  {
308  var attribute = element?.GetAttribute(name);
309  if (attribute == null) { return defaultValue; }
310 
311  string stringValue = attribute.Value;
312  if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
313 
314  string[] splitValue = stringValue.Split(',');
315  float[] floatValue = new float[splitValue.Length];
316  for (int i = 0; i < splitValue.Length; i++)
317  {
318  try
319  {
320  string strVal = splitValue[i];
321  if (strVal.LastOrDefault() == 'f')
322  {
323  strVal = strVal.Substring(0, strVal.Length - 1);
324  }
325  floatValue[i] = float.Parse(strVal, CultureInfo.InvariantCulture);
326  }
327  catch (Exception e)
328  {
329  LogAttributeError(attribute, element, e);
330  }
331  }
332 
333  return floatValue;
334  }
335 
336  public static bool TryGetAttributeInt(this XElement element, string name, out int result)
337  {
338  var attribute = element?.GetAttribute(name);
339  result = default;
340  if (attribute == null) { return false; }
341 
342  if (int.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var intVal))
343  {
344  result = intVal;
345  return true;
346  }
347  if (float.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var floatVal))
348  {
349  result = (int)floatVal;
350  return true;
351  }
352  return false;
353  }
354 
355  public static int GetAttributeInt(this XElement element, string name, int defaultValue) => GetAttributeInt(element?.GetAttribute(name), defaultValue);
356 
357  public static int GetAttributeInt(this XAttribute attribute, int defaultValue)
358  {
359  if (attribute == null) { return defaultValue; }
360 
361  int val = defaultValue;
362 
363  try
364  {
365  if (!Int32.TryParse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out val))
366  {
367  val = (int)float.Parse(attribute.Value, CultureInfo.InvariantCulture);
368  }
369  }
370  catch (Exception e)
371  {
372  LogAttributeError(attribute, attribute.Parent, e);
373  }
374 
375  return val;
376  }
377 
378  public static uint GetAttributeUInt(this XElement element, string name, uint defaultValue)
379  {
380  var attribute = element?.GetAttribute(name);
381  if (attribute == null) { return defaultValue; }
382 
383  uint val = defaultValue;
384 
385  try
386  {
387  val = UInt32.Parse(attribute.Value);
388  }
389  catch (Exception e)
390  {
391  LogAttributeError(attribute, element, e);
392  }
393 
394  return val;
395  }
396 
397  public static ushort GetAttributeUInt16(this XElement element, string name, ushort defaultValue)
398  {
399  var attribute = element?.GetAttribute(name);
400  if (attribute == null) { return defaultValue; }
401 
402  ushort val = defaultValue;
403 
404  try
405  {
406  val = ushort.Parse(attribute.Value);
407  }
408  catch (Exception e)
409  {
410  LogAttributeError(attribute, element, e);
411  }
412 
413  return val;
414  }
415 
416  public static UInt64 GetAttributeUInt64(this XElement element, string name, UInt64 defaultValue)
417  {
418  var attribute = element?.GetAttribute(name);
419  if (attribute == null) { return defaultValue; }
420 
421  UInt64 val = defaultValue;
422 
423  try
424  {
425  val = UInt64.Parse(attribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture);
426  }
427  catch (Exception e)
428  {
429  LogAttributeError(attribute, element, e);
430  }
431 
432  return val;
433  }
434 
435  public static Option<SerializableDateTime> GetAttributeDateTime(
436  this XElement element, string name)
437  {
438  var attribute = element?.GetAttribute(name);
439  if (attribute == null) { return Option<SerializableDateTime>.None(); }
440 
441  string attrVal = attribute.Value;
442  return SerializableDateTime.Parse(attrVal);
443  }
444 
445  public static Version GetAttributeVersion(this XElement element, string name, Version defaultValue)
446  {
447  var attribute = element?.GetAttribute(name);
448  if (attribute == null) { return defaultValue; }
449 
450  Version val = defaultValue;
451 
452  try
453  {
454  val = Version.Parse(attribute.Value);
455  }
456  catch (Exception e)
457  {
458  LogAttributeError(attribute, element, e);
459  }
460 
461  return val;
462  }
463 
464  public static int[] GetAttributeIntArray(this XElement element, string name, int[] defaultValue)
465  {
466  var attribute = element?.GetAttribute(name);
467  if (attribute == null) { return defaultValue; }
468 
469  string stringValue = attribute.Value;
470  if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
471 
472  string[] splitValue = stringValue.Split(',');
473  int[] intValue = new int[splitValue.Length];
474  for (int i = 0; i < splitValue.Length; i++)
475  {
476  try
477  {
478  int val = Int32.Parse(splitValue[i]);
479  intValue[i] = val;
480  }
481  catch (Exception e)
482  {
483  LogAttributeError(attribute, element, e);
484  }
485  }
486 
487  return intValue;
488  }
489  public static ushort[] GetAttributeUshortArray(this XElement element, string name, ushort[] defaultValue)
490  {
491  var attribute = element?.GetAttribute(name);
492  if (attribute == null) { return defaultValue; }
493 
494  string stringValue = attribute.Value;
495  if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
496 
497  string[] splitValue = stringValue.Split(',');
498  ushort[] ushortValue = new ushort[splitValue.Length];
499  for (int i = 0; i < splitValue.Length; i++)
500  {
501  try
502  {
503  ushort val = ushort.Parse(splitValue[i]);
504  ushortValue[i] = val;
505  }
506  catch (Exception e)
507  {
508  LogAttributeError(attribute, element, e);
509  }
510  }
511 
512  return ushortValue;
513  }
514 
515  public static T GetAttributeEnum<T>(this XElement element, string name, T defaultValue) where T : struct, Enum
516  {
517  var attr = element?.GetAttribute(name);
518  if (attr == null) { return defaultValue; }
519 
520  if (Enum.TryParse(attr.Value, true, out T result))
521  {
522  return result;
523  }
524  else if (int.TryParse(attr.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out int resultInt))
525  {
526  return Unsafe.As<int, T>(ref resultInt);
527  }
528  DebugConsole.ThrowError($"Error in {attr}! \"{attr}\" is not a valid {typeof(T).Name} value");
529  return default;
530 
531  }
532 
533  public static bool GetAttributeBool(this XElement element, string name, bool defaultValue)
534  {
535  var attribute = element?.GetAttribute(name);
536  if (attribute == null) { return defaultValue; }
537  return attribute.GetAttributeBool(defaultValue);
538  }
539 
540  public static bool GetAttributeBool(this XAttribute attribute, bool defaultValue)
541  {
542  if (attribute == null) { return defaultValue; }
543 
544  string val = attribute.Value.ToLowerInvariant().Trim();
545  if (val == "true")
546  {
547  return true;
548  }
549  if (val == "false")
550  {
551  return false;
552  }
553 
554  DebugConsole.ThrowError("Error in " + attribute.Value.ToString() + "! \"" + val + "\" is not a valid boolean value");
555  return false;
556  }
557 
558  public static Point GetAttributePoint(this XElement element, string name, Point defaultValue)
559  {
560  var attribute = element?.GetAttribute(name);
561  if (attribute == null) { return defaultValue; }
562  return ParsePoint(attribute.Value);
563  }
564 
565  public static Vector2 GetAttributeVector2(this XElement element, string name, Vector2 defaultValue)
566  {
567  var attribute = element?.GetAttribute(name);
568  if (attribute == null) { return defaultValue; }
569  return ParseVector2(attribute.Value);
570  }
571 
572  public static Vector3 GetAttributeVector3(this XElement element, string name, Vector3 defaultValue)
573  {
574  var attribute = element?.GetAttribute(name);
575  if (attribute == null) { return defaultValue; }
576  return ParseVector3(attribute.Value);
577  }
578 
579  public static Vector4 GetAttributeVector4(this XElement element, string name, Vector4 defaultValue)
580  {
581  var attribute = element?.GetAttribute(name);
582  if (attribute == null) { return defaultValue; }
583  return ParseVector4(attribute.Value);
584  }
585 
586  public static Color GetAttributeColor(this XElement element, string name, Color defaultValue)
587  {
588  var attribute = element?.GetAttribute(name);
589  if (attribute == null) { return defaultValue; }
590  return ParseColor(attribute.Value);
591  }
592 
593  public static Color? GetAttributeColor(this XElement element, string name)
594  {
595  var attribute = element?.GetAttribute(name);
596  if (attribute == null) { return null; }
597  return ParseColor(attribute.Value);
598  }
599 
600  public static Color[] GetAttributeColorArray(this XElement element, string name, Color[] defaultValue)
601  {
602  var attribute = element?.GetAttribute(name);
603  if (attribute == null) { return defaultValue; }
604 
605  string stringValue = attribute.Value;
606  if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
607 
608  string[] splitValue = stringValue.Split(';');
609  Color[] colorValue = new Color[splitValue.Length];
610  for (int i = 0; i < splitValue.Length; i++)
611  {
612  try
613  {
614  Color val = ParseColor(splitValue[i], true);
615  colorValue[i] = val;
616  }
617  catch (Exception e)
618  {
619  LogAttributeError(attribute, element, e);
620  }
621  }
622 
623  return colorValue;
624  }
625 
626  private static void LogAttributeError(XAttribute attribute, XElement element, Exception e)
627  {
628  string elementStr = element.ToString();
629  if (elementStr.Length > 500)
630  {
631  DebugConsole.ThrowError($"Error when reading attribute \"{attribute}\"!", e);
632  }
633  else
634  {
635  DebugConsole.ThrowError($"Error when reading attribute \"{attribute.Name}\" from {elementStr}!", e);
636  }
637  }
638 
639 #if CLIENT
640  public static KeyOrMouse GetAttributeKeyOrMouse(this XElement element, string name, KeyOrMouse defaultValue)
641  {
642  string strValue = element.GetAttributeString(name, defaultValue?.ToString() ?? "");
643  if (Enum.TryParse(strValue, true, out Microsoft.Xna.Framework.Input.Keys key))
644  {
645  return key;
646  }
647  else if (Enum.TryParse(strValue, out MouseButton mouseButton))
648  {
649  return mouseButton;
650  }
651  else if (int.TryParse(strValue, NumberStyles.Any, CultureInfo.InvariantCulture, out int mouseButtonInt) &&
652  Enum.GetValues<MouseButton>().Contains((MouseButton)mouseButtonInt))
653  {
654  return (MouseButton)mouseButtonInt;
655  }
656  else if (string.Equals(strValue, "LeftMouse", StringComparison.OrdinalIgnoreCase))
657  {
658  return !PlayerInput.MouseButtonsSwapped() ? MouseButton.PrimaryMouse : MouseButton.SecondaryMouse;
659  }
660  else if (string.Equals(strValue, "RightMouse", StringComparison.OrdinalIgnoreCase))
661  {
662  return !PlayerInput.MouseButtonsSwapped() ? MouseButton.SecondaryMouse : MouseButton.PrimaryMouse;
663  }
664  return defaultValue;
665  }
666 #endif
667 
668  public static Rectangle GetAttributeRect(this XElement element, string name, Rectangle defaultValue)
669  {
670  var attribute = element?.GetAttribute(name);
671  if (attribute == null) { return defaultValue; }
672  return ParseRect(attribute.Value, false);
673  }
674 
675  //TODO: nested tuples and and n-uples where n!=2 are unsupported
676  public static (T1, T2) GetAttributeTuple<T1, T2>(this XElement element, string name, (T1, T2) defaultValue)
677  {
678  string strValue = element.GetAttributeString(name, $"({defaultValue.Item1}, {defaultValue.Item2})").Trim();
679 
680  return ParseTuple(strValue, defaultValue);
681  }
682 
683  public static (T1, T2)[] GetAttributeTupleArray<T1, T2>(this XElement element, string name,
684  (T1, T2)[] defaultValue)
685  {
686  var attribute = element?.GetAttribute(name);
687  if (attribute == null) { return defaultValue; }
688 
689  string stringValue = attribute.Value;
690  if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
691 
692  return stringValue.Split(';').Select(s => ParseTuple<T1, T2>(s, default)).ToArray();
693  }
694 
695  public static Range<int> GetAttributeRange(this XElement element, string name, Range<int> defaultValue)
696  {
697  var attribute = element?.GetAttribute(name);
698  if (attribute is null) { return defaultValue; }
699 
700  string stringValue = attribute.Value;
701  return string.IsNullOrEmpty(stringValue) ? defaultValue : ParseRange(stringValue);
702  }
703 
704  public static string ElementInnerText(this XElement el)
705  {
706  StringBuilder str = new StringBuilder();
707  foreach (XNode element in el.DescendantNodes().Where(x => x.NodeType == XmlNodeType.Text))
708  {
709  str.Append(element.ToString());
710  }
711  return str.ToString();
712  }
713 
714  public static string PointToString(Point point)
715  {
716  return point.X.ToString() + "," + point.Y.ToString();
717  }
718 
719  public static string Vector2ToString(Vector2 vector)
720  {
721  return vector.X.ToString("G", CultureInfo.InvariantCulture) + "," + vector.Y.ToString("G", CultureInfo.InvariantCulture);
722  }
723 
724  public static string Vector3ToString(Vector3 vector, string format = "G")
725  {
726  return vector.X.ToString(format, CultureInfo.InvariantCulture) + "," +
727  vector.Y.ToString(format, CultureInfo.InvariantCulture) + "," +
728  vector.Z.ToString(format, CultureInfo.InvariantCulture);
729  }
730 
731  public static string Vector4ToString(Vector4 vector, string format = "G")
732  {
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);
737  }
738 
739  [Obsolete("Prefer XMLExtensions.ToStringHex")]
740  public static string ColorToString(Color color)
741  => $"{color.R},{color.G},{color.B},{color.A}";
742 
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}" : "");
746 
747  public static string RectToString(Rectangle rect)
748  {
749  return rect.X + "," + rect.Y + "," + rect.Width + "," + rect.Height;
750  }
751 
752  public static (T1, T2) ParseTuple<T1, T2>(string strValue, (T1, T2) defaultValue)
753  {
754  strValue = strValue.Trim();
755  //require parentheses
756  if (strValue[0] != '(' || strValue[^1] != ')') { return defaultValue; }
757  //remove parentheses
758  strValue = strValue[1..^1];
759 
760  string[] elems = strValue.Split(',');
761  if (elems.Length != 2) { return defaultValue; }
762 
763  return ((T1)Converters[typeof(T1)].Invoke(elems[0], defaultValue.Item1),
764  (T2)Converters[typeof(T2)].Invoke(elems[1], defaultValue.Item2));
765  }
766 
767  public static Point ParsePoint(string stringPoint, bool errorMessages = true)
768  {
769  string[] components = stringPoint.Split(',');
770  Point point = Point.Zero;
771 
772  if (components.Length != 2)
773  {
774  if (!errorMessages) { return point; }
775  DebugConsole.ThrowError("Failed to parse the string \"" + stringPoint + "\" to Vector2");
776  return point;
777  }
778 
779  int.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out point.X);
780  int.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out point.Y);
781  return point;
782  }
783 
784  public static Vector2 ParseVector2(string stringVector2, bool errorMessages = true)
785  {
786  string[] components = stringVector2.Split(',');
787 
788  Vector2 vector = Vector2.Zero;
789 
790  if (components.Length != 2)
791  {
792  if (!errorMessages) { return vector; }
793  DebugConsole.ThrowError("Failed to parse the string \"" + stringVector2 + "\" to Vector2");
794  return vector;
795  }
796 
797  float.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.X);
798  float.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Y);
799 
800  return vector;
801  }
802 
803  public static Vector3 ParseVector3(string stringVector3, bool errorMessages = true)
804  {
805  string[] components = stringVector3.Split(',');
806 
807  Vector3 vector = Vector3.Zero;
808 
809  if (components.Length != 3)
810  {
811  if (!errorMessages) { return vector; }
812  DebugConsole.ThrowError("Failed to parse the string \"" + stringVector3 + "\" to Vector3");
813  return vector;
814  }
815 
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);
819 
820  return vector;
821  }
822 
823  public static Vector4 ParseVector4(string stringVector4, bool errorMessages = true)
824  {
825  string[] components = stringVector4.Split(',');
826 
827  Vector4 vector = Vector4.Zero;
828 
829  if (components.Length < 3)
830  {
831  if (errorMessages) { DebugConsole.ThrowError("Failed to parse the string \"" + stringVector4 + "\" to Vector4"); }
832  return vector;
833  }
834 
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)
839  {
840  Single.TryParse(components[3], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.W);
841  }
842 
843  return vector;
844  }
845 
846  private static readonly ImmutableDictionary<Identifier, Color> monoGameColors =
847  typeof(Color)
848  .GetProperties(BindingFlags.Static | BindingFlags.Public)
849  .Where(p => p.PropertyType == typeof(Color))
850  .Select(p => (p.Name.ToIdentifier(), p.GetValueFromStaticProperty<Color>()))
851  .ToImmutableDictionary();
852 
853  public static Color ParseColor(string stringColor, bool errorMessages = true)
854  {
855  if (stringColor.StartsWith("gui.", StringComparison.OrdinalIgnoreCase))
856  {
857 #if CLIENT
858  Identifier colorName = stringColor.Substring(4).ToIdentifier();
859  if (GUIStyle.Colors.TryGetValue(colorName, out GUIColor guiColor))
860  {
861  return guiColor.Value;
862  }
863 #endif
864  return Color.White;
865  }
866  if (stringColor.StartsWith("faction.", StringComparison.OrdinalIgnoreCase))
867  {
868  Identifier factionId = stringColor.Substring(8).ToIdentifier();
869  if (FactionPrefab.Prefabs.TryGet(factionId, out var faction))
870  {
871  return faction.IconColor;
872  }
873  return Color.White;
874  }
875 
876  if (monoGameColors.TryGetValue(stringColor.ToIdentifier(), out var monoGameColor))
877  {
878  return monoGameColor;
879  }
880 
881  string[] strComponents = stringColor.Split(',');
882 
883  Color color = Color.White;
884 
885  float[] components = new float[4] { 1.0f, 1.0f, 1.0f, 1.0f };
886 
887  if (strComponents.Length == 1)
888  {
889  bool altParseFailed = true;
890  stringColor = stringColor.Trim();
891  if (stringColor.Length > 0 && stringColor[0] == '#')
892  {
893  stringColor = stringColor.Substring(1);
894 
895  if (int.TryParse(stringColor, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int colorInt))
896  {
897  if (stringColor.Length == 6)
898  {
899  colorInt = (colorInt << 8) | 0xff;
900  }
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;
905 
906  altParseFailed = false;
907  }
908  }
909  else if (stringColor.Length > 0 && stringColor[0] == '{')
910  {
911  stringColor = stringColor.Substring(1, stringColor.Length-2);
912 
913  string[] mgComponents = stringColor.Split(' ');
914  if (mgComponents.Length == 4)
915  {
916  altParseFailed = false;
917 
918  string[] expectedPrefixes = {"R:", "G:", "B:", "A:"};
919  for (int i = 0; i < 4; i++)
920  {
921  if (mgComponents[i].StartsWith(expectedPrefixes[i], StringComparison.OrdinalIgnoreCase))
922  {
923  string strToParse = mgComponents[i]
924  .Remove(expectedPrefixes[i], StringComparison.OrdinalIgnoreCase)
925  .Trim();
926  int val = 0;
927  altParseFailed |= !int.TryParse(strToParse, out val);
928  components[i] = ((float) val) / 255f;
929  }
930  else
931  {
932  altParseFailed = true;
933  break;
934  }
935  }
936  }
937  }
938 
939  if (altParseFailed)
940  {
941  if (errorMessages) { DebugConsole.ThrowError("Failed to parse the string \"" + stringColor + "\" to Color"); }
942  return Color.White;
943  }
944  }
945  else
946  {
947  for (int i = 0; i < 4 && i < strComponents.Length; i++)
948  {
949  float.TryParse(strComponents[i], NumberStyles.Float, CultureInfo.InvariantCulture, out components[i]);
950  }
951 
952  if (components.Any(c => c > 1.0f))
953  {
954  for (int i = 0; i < 4; i++)
955  {
956  components[i] = components[i] / 255.0f;
957  }
958  //alpha defaults to 1.0 if not given
959  if (strComponents.Length < 4) components[3] = 1.0f;
960  }
961  }
962 
963  return new Color(components[0], components[1], components[2], components[3]);
964  }
965 
966  public static Rectangle ParseRect(string stringRect, bool requireSize, bool errorMessages = true)
967  {
968  string[] strComponents = stringRect.Split(',');
969  if ((strComponents.Length < 3 && requireSize) || strComponents.Length < 2)
970  {
971  if (errorMessages) { DebugConsole.ThrowError("Failed to parse the string \"" + stringRect + "\" to Rectangle"); }
972  return new Rectangle(0, 0, 0, 0);
973  }
974 
975  int[] components = new int[4] { 0, 0, 0, 0 };
976  for (int i = 0; i < 4 && i < strComponents.Length; i++)
977  {
978  int.TryParse(strComponents[i], out components[i]);
979  }
980 
981  return new Rectangle(components[0], components[1], components[2], components[3]);
982  }
983 
984  public static float[] ParseFloatArray(string[] stringArray)
985  {
986  if (stringArray == null || stringArray.Length == 0) return null;
987 
988  float[] floatArray = new float[stringArray.Length];
989  for (int i = 0; i < floatArray.Length; i++)
990  {
991  floatArray[i] = 0.0f;
992  Single.TryParse(stringArray[i], NumberStyles.Float, CultureInfo.InvariantCulture, out floatArray[i]);
993  }
994 
995  return floatArray;
996  }
997 
998  // parse a range string, e.g "1-3" or "3"
999  public static Range<int> ParseRange(string rangeString)
1000  {
1001  if (string.IsNullOrWhiteSpace(rangeString)) { return GetDefault(rangeString); }
1002 
1003  string[] split = rangeString.Split('-');
1004  return split.Length switch
1005  {
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)
1009  };
1010 
1011  static bool TryParseInt(string value, out int result)
1012  {
1013  if (!string.IsNullOrWhiteSpace(value))
1014  {
1015  return int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
1016  }
1017 
1018  result = default;
1019  return false;
1020  }
1021 
1022  static Range<int> GetDefault(string rangeString)
1023  {
1024  DebugConsole.ThrowError($"Error parsing range: \"{rangeString}\" (using default value 0-99)");
1025  return new Range<int>(0, 99);
1026  }
1027  }
1028 
1029  public static Identifier VariantOf(this XElement element) =>
1030  element.GetAttributeIdentifier("inherit", element.GetAttributeIdentifier("variantof", ""));
1031 
1032  public static string[] ParseStringArray(string stringArrayValues)
1033  {
1034  return string.IsNullOrEmpty(stringArrayValues) ? Array.Empty<string>() : stringArrayValues.Split(';');
1035  }
1036 
1037  public static Identifier[] ParseIdentifierArray(string stringArrayValues)
1038  {
1039  return ParseStringArray(stringArrayValues).ToIdentifiers().ToArray();
1040  }
1041 
1042  public static bool IsOverride(this XElement element) => element.NameAsIdentifier() == "override";
1043 
1048  public static XElement GetRootExcludingOverride(this XDocument doc) => doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
1049 
1050  public static XElement FirstElement(this XElement element) => element.Elements().FirstOrDefault();
1051 
1052  public static XAttribute GetAttribute(this XElement element, string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => element.GetAttribute(a => a.Name.ToString().Equals(name, comparisonMethod));
1053 
1054  public static void SetAttributeValue(this XElement element, string name, object value, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => GetAttribute(element, name, comparisonMethod)?.SetValue(value);
1055 
1056  public static XAttribute GetAttribute(this XElement element, Identifier name) => element.GetAttribute(name.Value, StringComparison.OrdinalIgnoreCase);
1057 
1058  public static XAttribute GetAttribute(this XElement element, Func<XAttribute, bool> predicate) => element.Attributes().FirstOrDefault(predicate);
1059 
1063  public static XElement GetChildElement(this XContainer container, string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => container.Elements().FirstOrDefault(e => e.Name.ToString().Equals(name, comparisonMethod));
1064 
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));
1069 
1070  public static IEnumerable<XElement> GetChildElements(this XContainer container, params string[] names)
1071  {
1072  return names.SelectMany(name => container.GetChildElements(name));
1073  }
1074 
1075  public static bool ComesAfter(this XElement element, XElement other)
1076  {
1077  if (element.Parent != other.Parent) { return false; }
1078  foreach (var child in element.Parent.Elements())
1079  {
1080  if (child == element) { return false; }
1081  if (child == other) { return true; }
1082  }
1083  return false;
1084  }
1085 
1086  public static Identifier NameAsIdentifier(this XElement elem)
1087  {
1088  return elem.Name.LocalName.ToIdentifier();
1089  }
1090 
1091  public static Identifier NameAsIdentifier(this XAttribute attr)
1092  {
1093  return attr.Name.LocalName.ToIdentifier();
1094  }
1095  }
1096 }