You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
8.9 KiB
C#
288 lines
8.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Resources;
|
|
|
|
using YamlDotNet.RepresentationModel;
|
|
|
|
//Yaml流 扩展功能
|
|
namespace OptionStudy.UnitApp
|
|
{
|
|
/// <summary>
|
|
/// Yaml流 配置源
|
|
/// </summary>
|
|
public class YamlStreamConfigurationSource : StreamConfigurationSource
|
|
{
|
|
/// <summary>
|
|
/// Builds the <see cref="YamlStreamConfigurationProvider"/> for this source.
|
|
/// </summary>
|
|
/// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
|
|
/// <returns>An <see cref="YamlStreamConfigurationProvider"/></returns>
|
|
public override IConfigurationProvider Build(IConfigurationBuilder builder)
|
|
{
|
|
return new YamlStreamConfigurationProvider(this);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Yaml流配置 提供者
|
|
/// </summary>
|
|
public class YamlStreamConfigurationProvider : StreamConfigurationProvider
|
|
{
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="source">The <see cref="YamlStreamConfigurationSource"/>.
|
|
/// </param>
|
|
public YamlStreamConfigurationProvider(YamlStreamConfigurationSource source) : base(source) { }
|
|
|
|
/// <summary>
|
|
/// Loads json configuration key/values from a stream into a provider.
|
|
/// </summary>
|
|
/// <param name="stream">The json <see cref="Stream"/> to load configuration data from.</param>
|
|
public override void Load(Stream stream)
|
|
{
|
|
var parser = new YamlFileParser();
|
|
Data = parser?.Parse(stream)??new Dictionary<string, string?>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Yaml流 扩展方法类
|
|
/// </summary>
|
|
public static class YamlConfigurationExtensions
|
|
{
|
|
public static IConfigurationBuilder AddYamlStreamFile(this IConfigurationBuilder builder, Stream stream)
|
|
{
|
|
Stream stream2 = stream;
|
|
return builder.Add(delegate (YamlStreamConfigurationSource s)
|
|
{
|
|
s.Stream = stream2;
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 解析类:复制原库,因为原库类为 internal
|
|
/// </summary>
|
|
public class YamlFileParser
|
|
{
|
|
private readonly IDictionary<string, string?> _data = new SortedDictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
|
private readonly Stack<string> _context = new Stack<string>();
|
|
private string? _currentPath;
|
|
|
|
public IDictionary<string, string?> Parse(Stream input)
|
|
{
|
|
_data.Clear();
|
|
_context.Clear();
|
|
|
|
// https://dotnetfiddle.net/rrR2Bb
|
|
var yaml = new YamlStream();
|
|
yaml.Load(new StreamReader(input, detectEncodingFromByteOrderMarks: true));
|
|
|
|
if (yaml.Documents.Any())
|
|
{
|
|
var mapping = (YamlMappingNode)yaml.Documents[0].RootNode;
|
|
|
|
// The document node is a mapping node
|
|
VisitYamlMappingNode(mapping);
|
|
}
|
|
|
|
return _data;
|
|
}
|
|
|
|
private void VisitYamlNodePair(KeyValuePair<YamlNode, YamlNode> yamlNodePair)
|
|
{
|
|
var context = ((YamlScalarNode)yamlNodePair.Key).Value??string.Empty;
|
|
VisitYamlNode(context, yamlNodePair.Value);
|
|
}
|
|
|
|
private void VisitYamlNode(string context, YamlNode node)
|
|
{
|
|
if (node is YamlScalarNode scalarNode)
|
|
{
|
|
VisitYamlScalarNode(context, scalarNode);
|
|
}
|
|
if (node is YamlMappingNode mappingNode)
|
|
{
|
|
VisitYamlMappingNode(context, mappingNode);
|
|
}
|
|
if (node is YamlSequenceNode sequenceNode)
|
|
{
|
|
VisitYamlSequenceNode(context, sequenceNode);
|
|
}
|
|
}
|
|
|
|
private void VisitYamlScalarNode(string context, YamlScalarNode yamlValue)
|
|
{
|
|
//a node with a single 1-1 mapping
|
|
EnterContext(context);
|
|
var currentKey = _currentPath??string.Empty;
|
|
|
|
if (_data.ContainsKey(currentKey))
|
|
{
|
|
throw new FormatException(Resources.FormatError_KeyIsDuplicated(currentKey));
|
|
}
|
|
|
|
_data[currentKey] = IsNullValue(yamlValue) ? null : yamlValue.Value;
|
|
ExitContext();
|
|
}
|
|
|
|
private void VisitYamlMappingNode(YamlMappingNode node)
|
|
{
|
|
foreach (var yamlNodePair in node.Children)
|
|
{
|
|
VisitYamlNodePair(yamlNodePair);
|
|
}
|
|
}
|
|
|
|
private void VisitYamlMappingNode(string context, YamlMappingNode yamlValue)
|
|
{
|
|
//a node with an associated sub-document
|
|
EnterContext(context);
|
|
|
|
VisitYamlMappingNode(yamlValue);
|
|
|
|
ExitContext();
|
|
}
|
|
|
|
private void VisitYamlSequenceNode(string context, YamlSequenceNode yamlValue)
|
|
{
|
|
//a node with an associated list
|
|
EnterContext(context);
|
|
|
|
VisitYamlSequenceNode(yamlValue);
|
|
|
|
ExitContext();
|
|
}
|
|
|
|
private void VisitYamlSequenceNode(YamlSequenceNode node)
|
|
{
|
|
for (int i = 0; i < node.Children.Count; i++)
|
|
{
|
|
VisitYamlNode(i.ToString(), node.Children[i]);
|
|
}
|
|
}
|
|
|
|
private void EnterContext(string context)
|
|
{
|
|
_context.Push(context);
|
|
_currentPath = ConfigurationPath.Combine(_context.Reverse());
|
|
}
|
|
|
|
private void ExitContext()
|
|
{
|
|
_context.Pop();
|
|
_currentPath = ConfigurationPath.Combine(_context.Reverse());
|
|
}
|
|
|
|
private bool IsNullValue(YamlScalarNode yamlValue)
|
|
{
|
|
return yamlValue.Style == YamlDotNet.Core.ScalarStyle.Plain
|
|
&& (
|
|
yamlValue.Value == "~"
|
|
|| yamlValue.Value == "null"
|
|
|| yamlValue.Value == "Null"
|
|
|| yamlValue.Value == "NULL"
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 解析依赖类:复制原库,因为原库类为 internal
|
|
/// </summary>
|
|
public static class Resources
|
|
{
|
|
private static readonly ResourceManager _resourceManager
|
|
= new ResourceManager("NetEscapades.Configuration.Yaml.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
|
|
|
/// <summary>
|
|
/// The configuration file '{0}' was not found and is not optional.
|
|
/// </summary>
|
|
internal static string Error_FileNotFound
|
|
{
|
|
get { return GetString("Error_FileNotFound"); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The configuration file '{0}' was not found and is not optional.
|
|
/// </summary>
|
|
internal static string FormatError_FileNotFound(object p0)
|
|
{
|
|
return string.Format(CultureInfo.CurrentCulture, GetString("Error_FileNotFound"), p0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// File path must be a non-empty string.
|
|
/// </summary>
|
|
internal static string Error_InvalidFilePath
|
|
{
|
|
get { return GetString("Error_InvalidFilePath"); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// File path must be a non-empty string.
|
|
/// </summary>
|
|
internal static string FormatError_InvalidFilePath()
|
|
{
|
|
return GetString("Error_InvalidFilePath");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Could not parse the Yaml file. Error on line number '{0}': '{1}'.
|
|
/// </summary>
|
|
internal static string Error_YamlParseError
|
|
{
|
|
get { return GetString("Error_YamlParseError"); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Could not parse the YAML file: {0}.
|
|
/// </summary>
|
|
internal static string FormatError_YamlParseError(object p0)
|
|
{
|
|
return string.Format(CultureInfo.CurrentCulture, GetString("Error_YamlParseError"), p0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A duplicate key '{0}' was found.
|
|
/// </summary>
|
|
internal static string Error_KeyIsDuplicated
|
|
{
|
|
get { return GetString("Error_KeyIsDuplicated"); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// A duplicate key '{0}' was found.
|
|
/// </summary>
|
|
internal static string FormatError_KeyIsDuplicated(object p0)
|
|
{
|
|
return string.Format(CultureInfo.CurrentCulture, GetString("Error_KeyIsDuplicated"), p0);
|
|
}
|
|
|
|
private static string GetString(string name, params string[] formatterNames)
|
|
{
|
|
var value = _resourceManager.GetString(name);
|
|
|
|
System.Diagnostics.Debug.Assert(value != null);
|
|
|
|
if (formatterNames != null)
|
|
{
|
|
for (var i = 0; i < formatterNames.Length; i++)
|
|
{
|
|
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}
|
|
}
|