NLDClient-yudde/ProjectNLD/Assets/ThirdParty/YamlDotNet/Serialization/NodeDeserializers/ScalarNodeDeserializer.cs

439 lines
16 KiB
C#

// This file is part of YamlDotNet - A .NET library for YAML.
// Copyright (c) Antoine Aubry and contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.ObjectPool;
using YamlDotNet.Helpers;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.Utilities;
namespace YamlDotNet.Serialization.NodeDeserializers
{
public sealed class ScalarNodeDeserializer : INodeDeserializer
{
private const string BooleanTruePattern = "^(true|y|yes|on)$";
private const string BooleanFalsePattern = "^(false|n|no|off)$";
private readonly bool attemptUnknownTypeDeserialization;
private readonly ITypeConverter typeConverter;
private readonly ITypeInspector typeInspector;
private readonly YamlFormatter formatter;
private readonly INamingConvention enumNamingConvention;
public ScalarNodeDeserializer(bool attemptUnknownTypeDeserialization,
ITypeConverter typeConverter,
ITypeInspector typeInspector,
YamlFormatter formatter,
INamingConvention enumNamingConvention)
{
this.attemptUnknownTypeDeserialization = attemptUnknownTypeDeserialization;
this.typeConverter = typeConverter ?? throw new ArgumentNullException(nameof(typeConverter));
this.typeInspector = typeInspector;
this.formatter = formatter;
this.enumNamingConvention = enumNamingConvention;
}
public bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer)
{
if (!parser.TryConsume<Scalar>(out var scalar))
{
value = null;
return false;
}
// Strip off the nullable & fsharp option type, if present
var underlyingType = Nullable.GetUnderlyingType(expectedType)
?? FsharpHelper.GetOptionUnderlyingType(expectedType)
?? expectedType;
if (underlyingType.IsEnum())
{
var enumName = enumNamingConvention.Reverse(scalar.Value);
enumName = typeInspector.GetEnumName(underlyingType, enumName);
value = Enum.Parse(underlyingType, enumName, true);
return true;
}
var typeCode = underlyingType.GetTypeCode();
switch (typeCode)
{
case TypeCode.Boolean:
value = DeserializeBooleanHelper(scalar.Value);
break;
case TypeCode.Byte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
value = DeserializeIntegerHelper(typeCode, scalar.Value);
break;
case TypeCode.Single:
value = float.Parse(scalar.Value, formatter.NumberFormat);
break;
case TypeCode.Double:
value = double.Parse(scalar.Value, formatter.NumberFormat);
break;
case TypeCode.Decimal:
value = decimal.Parse(scalar.Value, formatter.NumberFormat);
break;
case TypeCode.String:
value = scalar.Value;
break;
case TypeCode.Char:
value = scalar.Value[0];
break;
case TypeCode.DateTime:
// TODO: This is probably incorrect. Use the correct regular expression.
value = DateTime.Parse(scalar.Value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
break;
default:
if (expectedType == typeof(object))
{
if (!scalar.IsKey && attemptUnknownTypeDeserialization)
{
value = AttemptUnknownTypeDeserialization(scalar);
}
else
{
// Default to string
value = scalar.Value;
}
}
else
{
value = typeConverter.ChangeType(scalar.Value, expectedType, enumNamingConvention, typeInspector);
}
break;
}
return true;
}
private static bool DeserializeBooleanHelper(string value)
{
bool result;
if (Regex.IsMatch(value, BooleanTruePattern, RegexOptions.IgnoreCase))
{
result = true;
}
else if (Regex.IsMatch(value, BooleanFalsePattern, RegexOptions.IgnoreCase))
{
result = false;
}
else
{
throw new FormatException($"The value \"{value}\" is not a valid YAML Boolean");
}
return result;
}
private object DeserializeIntegerHelper(TypeCode typeCode, string value)
{
using var numberBuilderStringBuilder = StringBuilderPool.Rent();
var numberBuilder = numberBuilderStringBuilder.Builder;
var currentIndex = 0;
var isNegative = false;
int numberBase;
ulong result = 0;
if (value[0] == '-')
{
currentIndex++;
isNegative = true;
}
else if (value[0] == '+')
{
currentIndex++;
}
if (value[currentIndex] == '0')
{
// Could be binary, octal, hex, decimal (0)
// If there are no characters remaining, it's a decimal zero
if (currentIndex == value.Length - 1)
{
numberBase = 10;
result = 0;
}
else
{
// Check the next character
currentIndex++;
if (value[currentIndex] == 'b')
{
// Binary
numberBase = 2;
currentIndex++;
}
else if (value[currentIndex] == 'x')
{
// Hex
numberBase = 16;
currentIndex++;
}
else
{
// Octal
numberBase = 8;
}
}
// Copy remaining digits to the number buffer (skip underscores)
while (currentIndex < value.Length)
{
if (value[currentIndex] != '_')
{
numberBuilder.Append(value[currentIndex]);
}
currentIndex++;
}
// Parse the magnitude of the number
switch (numberBase)
{
case 2:
case 8:
// TODO: how to incorporate the numberFormat?
result = Convert.ToUInt64(numberBuilder.ToString(), numberBase);
break;
case 16:
result = ulong.Parse(numberBuilder.ToString(), NumberStyles.HexNumber, formatter.NumberFormat);
break;
case 10:
// Result is already zero
break;
}
}
else
{
// Could be decimal or base 60
var chunks = value.Substring(currentIndex).Split(':');
result = 0;
for (var chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++)
{
result *= 60;
// TODO: verify that chunks after the first are non-negative and less than 60
result += ulong.Parse(chunks[chunkIndex].Replace("_", ""), CultureInfo.InvariantCulture);
}
}
if (isNegative)
{
long toCast;
// we do this because abs(long.minvalue) is 1 more than long.maxvalue.
if (result == 9223372036854775808) // abs(long.minvalue) => ulong
{
toCast = long.MinValue;
}
else
{
// this will throw if it's too big.
toCast = checked(-(long)result);
}
return CastInteger(toCast, typeCode);
}
else
{
return CastInteger(result, typeCode);
}
}
private static object CastInteger(long number, TypeCode typeCode)
{
checked
{
return typeCode switch
{
TypeCode.Byte => (byte)number,
TypeCode.Int16 => (short)number,
TypeCode.Int32 => (int)number,
TypeCode.Int64 => number,
TypeCode.SByte => (sbyte)number,
TypeCode.UInt16 => (ushort)number,
TypeCode.UInt32 => (uint)number,
TypeCode.UInt64 => (ulong)number,
_ => number,
};
}
}
private static object CastInteger(ulong number, TypeCode typeCode)
{
checked
{
return typeCode switch
{
TypeCode.Byte => (byte)number,
TypeCode.Int16 => (short)number,
TypeCode.Int32 => (int)number,
TypeCode.Int64 => (long)number,
TypeCode.SByte => (sbyte)number,
TypeCode.UInt16 => (ushort)number,
TypeCode.UInt32 => (uint)number,
TypeCode.UInt64 => number,
_ => number,
};
}
}
private object? AttemptUnknownTypeDeserialization(Scalar value)
{
if (value.Style == ScalarStyle.SingleQuoted ||
value.Style == ScalarStyle.DoubleQuoted ||
value.Style == ScalarStyle.Folded)
{
return value.Value;
}
var v = value.Value;
object? result;
switch (v)
{
case "":
case "~":
case "null":
case "Null":
case "NULL":
return null;
case "true":
case "True":
case "TRUE":
return true;
case "false":
case "False":
case "FALSE":
return false;
default:
if (Regex.IsMatch(v, "0x[0-9a-fA-F]+")) //base16 number
{
if (TryAndSwallow(() => Convert.ToByte(v, 16), out result)) { }
else if (TryAndSwallow(() => Convert.ToInt16(v, 16), out result)) { }
else if (TryAndSwallow(() => Convert.ToInt32(v, 16), out result)) { }
else if (TryAndSwallow(() => Convert.ToInt64(v, 16), out result)) { }
else if (TryAndSwallow(() => Convert.ToUInt64(v, 16), out result)) { }
else
{
//we couldn't parse it, default to a string. It's probably to big.
result = v;
}
}
else if (Regex.IsMatch(v, "0o[0-9a-fA-F]+")) //base8 number
{
if (TryAndSwallow(() => Convert.ToByte(v, 8), out result)) { }
else if (TryAndSwallow(() => Convert.ToInt16(v, 8), out result)) { }
else if (TryAndSwallow(() => Convert.ToInt32(v, 8), out result)) { }
else if (TryAndSwallow(() => Convert.ToInt64(v, 8), out result)) { }
else if (TryAndSwallow(() => Convert.ToUInt64(v, 8), out result)) { }
else
{
//we couldn't parse it, default to a string. It's probably to big.
result = v;
}
}
else if (Regex.IsMatch(v, @"[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?")) //regular number
{
if (TryAndSwallow(() => byte.Parse(v, formatter.NumberFormat), out result)) { }
else if (TryAndSwallow(() => short.Parse(v, formatter.NumberFormat), out result)) { }
else if (TryAndSwallow(() => int.Parse(v, formatter.NumberFormat), out result)) { }
else if (TryAndSwallow(() => long.Parse(v, formatter.NumberFormat), out result)) { }
else if (TryAndSwallow(() => ulong.Parse(v, formatter.NumberFormat), out result)) { }
else if (TryAndSwallow(() => float.Parse(v, formatter.NumberFormat), out result)) { }
else if (TryAndSwallow(() => double.Parse(v, formatter.NumberFormat), out result)) { }
else
{
//we couldn't parse it, default to string, It's probably too big
result = v;
}
}
else if (Regex.IsMatch(v, @"^[-+]?(\.inf|\.Inf|\.INF)$")) //infinities
{
if (v.StartsWith('-'))
{
result = float.NegativeInfinity;
}
else
{
result = float.PositiveInfinity;
}
}
else if (Regex.IsMatch(v, @"^(\.nan|\.NaN|\.NAN)$")) //not a number
{
result = float.NaN;
}
else
{
// not a known type, so make it a string.
result = v;
}
break;
}
return result;
}
private static bool TryAndSwallow(Func<object> attempt, out object? value)
{
try
{
value = attempt();
return true;
}
catch
{
value = null;
return false;
}
}
}
}