283 lines
9.4 KiB
C#
283 lines
9.4 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;
|
|
using System.Text.RegularExpressions;
|
|
using YamlDotNet.Core;
|
|
using YamlDotNet.Core.Events;
|
|
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)$";
|
|
|
|
bool INodeDeserializer.Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
|
|
{
|
|
if (!parser.TryConsume<Scalar>(out var scalar))
|
|
{
|
|
value = null;
|
|
return false;
|
|
}
|
|
|
|
// Strip off the nullable type, if present
|
|
var underlyingType = Nullable.GetUnderlyingType(expectedType) ?? expectedType;
|
|
|
|
if (underlyingType.IsEnum())
|
|
{
|
|
value = Enum.Parse(underlyingType, scalar.Value, 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, YamlFormatter.NumberFormat);
|
|
break;
|
|
|
|
case TypeCode.Double:
|
|
value = double.Parse(scalar.Value, YamlFormatter.NumberFormat);
|
|
break;
|
|
|
|
case TypeCode.Decimal:
|
|
value = decimal.Parse(scalar.Value, YamlFormatter.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))
|
|
{
|
|
// Default to string
|
|
value = scalar.Value;
|
|
}
|
|
else
|
|
{
|
|
value = TypeConverter.ChangeType(scalar.Value, expectedType);
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private object 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)
|
|
{
|
|
var numberBuilder = new StringBuilder();
|
|
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, YamlFormatter.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("_", ""));
|
|
}
|
|
}
|
|
|
|
if (isNegative)
|
|
{
|
|
return CastInteger(checked(-(long)result), 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,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|