using System; using System.Collections.Generic; using System.Globalization; using System.Xml; using UnityEngine; using Ubisoft.Hotel.Localisation; using Ubisoft.Hotel.Localisation.Editor; namespace Ubisoft.Hotel.Package.Editor { /// /// Class responsible for storing an InfoPlist item. /// /// It is used as a workaround to avoid the following warning message shown by Unity when a Serializable object contains instances of other /// Serializable objects of the same type (recursivity): /// "Serialization depth limit 10 exceeded at 'Ubisoft.Hotel.Package.Editor::InfoPlistProperty.m_valueAsList'. There may be an object composition cycle in one or more of your serialized classes." /// [Serializable] public class InfoPlistItem { private static string BoolToString(bool value) { return value ? "1" : "0"; } private static bool StringToBool(string value) { return value == "1"; } private static string IntToString(int value) { return value.ToString(); } private static int StringToInt(string value) { return HtInt.Parse(value); } private static string FloatToString(float value) { return value.ToString(); } private static float StringToFloat(string value) { return HtFloat.Parse(value); } private static string DoubleToString(double value) { return value.ToString(); } private static double StringToDouble(string value) { return HtDouble.Parse(value); } internal enum EType { None, String, LocalisedString, Boolean, Number, Array, Dictionary }; public const string ATTRIBUTE_KEY = "m_key"; public const string ATTRIBUTE_TYPE = "m_type"; public const string ATTRIBUTE_VALUE = "m_value"; public const string ATTRIBUTE_VALUE_AS_LOCALISED_STRING = "m_valueAsLocalisedString"; [SerializeField] private string m_key; [SerializeField] private EType m_type; [SerializeField] private string m_value; [SerializeField] private List m_valueAsLocalisedString; internal InfoPlistItem(string key, EType type, string value) { Key = key; Type = type; Value = value; } public InfoPlistItem(string key, string value) : this(key, EType.String, value) { } public InfoPlistItem(string key, bool value) : this(key, EType.Boolean, BoolToString(value)) { } public InfoPlistItem(string key, int value) : this(key, EType.Number, IntToString(value)) { } public InfoPlistItem(string key, float value) : this(key, EType.Number, FloatToString(value)) { } public InfoPlistItem(string key, double value) : this(key, EType.Number, DoubleToString(value)) { } public InfoPlistItem(string key, List value) : this(key, EType.LocalisedString, null) { ValueAsLocalisedString = value; } public InfoPlistItem Clone() { InfoPlistItem returnValue = new InfoPlistItem(Key, Type, Value); if (ValueAsLocalisedString != null) { returnValue.ValueAsLocalisedString = new List(); int count = ValueAsLocalisedString.Count; for (int i = 0; i < count; i++) { returnValue.ValueAsLocalisedString.Add(ValueAsLocalisedString[i]); } } return returnValue; } public string Key { get { return m_key; } set { m_key = value; } } internal EType Type { get { return m_type; } set { m_type = value; } } public string TypeAsString { get { return Type.ToString(); } } public string Value { get { return m_value; } set { m_value = value; } } public bool ValueAsBool { get { return StringToBool(Value); } set { Value = BoolToString(value); } } public int ValueAsInt { get { return StringToInt(Value); } set { Value = IntToString(value); } } public float ValueAsFloat { get { return StringToFloat(Value); } set { Value = FloatToString(value); } } public double ValueAsDouble { get { return StringToDouble(Value); } set { Value = DoubleToString(value); } } public List ValueAsLocalisedString { get { return m_valueAsLocalisedString; } set { m_valueAsLocalisedString = value; } } public bool Validate() { bool returnValue = true; EType enumType = Type; switch (enumType) { case EType.Boolean: returnValue = Value == "0" || Value == "1"; if (!returnValue) { Value = "0"; } break; case EType.Number: returnValue = float.TryParse(Value, NumberStyles.Any, CultureInfo.InvariantCulture, out _); if (!returnValue) { Value = "0"; } break; case EType.LocalisedString: // LocalisedString properties use ValueAsLocalisedString instead of Value. Value is set to empty to avoid misunderstandings if (!string.IsNullOrEmpty(Value)) { Value = ""; returnValue = false; } break; } if (enumType != EType.LocalisedString && ValueAsLocalisedString != null) { if (ValueAsLocalisedString != null) { ValueAsLocalisedString.Clear(); } returnValue = false; } return returnValue; } public bool IsValid() { bool returnValue = !string.IsNullOrEmpty(Key) && IsTypeValid(); if (returnValue) { if (IsLocalisedStringType()) { returnValue = IsValueAsLocalisedStringValid(); } else if (!IsMultipleType()) { returnValue = !string.IsNullOrEmpty(Value); } } return returnValue; } public bool IsTypeValid() { return Type != EType.None; } public bool IsMultipleType() { return Type == EType.Array || Type == EType.Dictionary; } public bool IsLocalisedStringType() { return Type == EType.LocalisedString; } public bool IsValueAsLocalisedStringValid() { return ValueAsLocalisedString != null && ValueAsLocalisedString.Count > 0; } public XmlElement GetValueNode(XmlDocument xmlDocument) { XmlElement returnValue = null; var enumType = Type; switch (enumType) { case EType.String: returnValue = xmlDocument.CreateElement("string"); returnValue.InnerText = Value; break; case EType.Boolean: string valueDecorator = ValueAsBool ? "true" : "false"; returnValue = xmlDocument.CreateElement(valueDecorator); break; case EType.Number: float floatValue; bool isInt = int.TryParse(Value, NumberStyles.Any, CultureInfo.InvariantCulture, out _); bool isFloat = float.TryParse(Value, NumberStyles.Any, CultureInfo.InvariantCulture, out floatValue); if (isInt && !Value.Contains(".")) { returnValue = xmlDocument.CreateElement("integer"); returnValue.InnerText = Value; } else if (isFloat) { returnValue = xmlDocument.CreateElement("real"); returnValue.InnerText = floatValue.ToString(); } break; case EType.LocalisedString: returnValue = xmlDocument.CreateElement("string"); string value = ""; if (ValueAsLocalisedString != null) { int count = ValueAsLocalisedString.Count; for (int i = 0; i < count && string.IsNullOrEmpty(value); i++) { if (ValueAsLocalisedString[i].LanguageId == LocalisationUtils.DEFAULT_LANGUAGE_ID) { value = ValueAsLocalisedString[i].Value; } } } returnValue.InnerText = value; break; } return returnValue; } public string GetDebugIsValidDescription() { bool isKeyValid = !string.IsNullOrEmpty(Key); string detail = null; if (IsLocalisedStringType()) { if (!IsValueAsLocalisedStringValid()) { detail = $" {Debug.FormatTextInCodeContext("ValueAsLocalisedString")} mustn't be empty for a {Debug.FormatTextInCodeContext("LocalisedString")} property"; } } else if (!IsMultipleType() && string.IsNullOrEmpty(Value)) { detail = " Value mustn't be empty for a non multiple type property"; } return $"Key: {Debug.FormatTextInUserContext(Key)} IsKeyValid: {isKeyValid} | Type: {Type} IsTypeValid: {IsTypeValid()} | Detail : {detail}"; } } }