priprava - v2

This commit is contained in:
2024-06-02 05:35:03 +02:00
parent 5167aa05cf
commit 5a2b5bae31
105 changed files with 22992 additions and 142 deletions

View File

@ -0,0 +1,34 @@
using System;
namespace MadMilkman.Ini
{
/// <summary>
/// Indicates the behavior of public property when serializing or deserializing the object that contains it.
/// </summary>
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class IniSerializationAttribute : Attribute
{
/// <summary>
/// Gets the <see cref="IniKey"/> name of serialized the property.
/// </summary>
public string Alias { get; private set; }
/// <summary>
/// Gets the value indicating whether serialization is ignored.
/// </summary>
public bool Ignore { get; private set; }
/// <summary>
/// Initializes a new instance of the IniSerializationAttribute class and specifies the <see cref="IniKey"/>'s name.
/// </summary>
/// <param name="alias">The name of the generated <see cref="IniKey"/>.</param>
public IniSerializationAttribute(string alias) { this.Alias = alias; }
/// <summary>
/// Initializes a new instance of the IniSerializationAttribute class and specifies if serialization is ignored.
/// </summary>
/// <param name="ignore">The value indicating whether serialization is ignored.</param>
public IniSerializationAttribute(bool ignore) { this.Ignore = ignore; }
}
}

View File

@ -0,0 +1,128 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
namespace MadMilkman.Ini
{
internal static class IniSerializer
{
private static readonly Predicate<Type> PropertyTypeVerifier = IniKey.IsSupportedValueType;
public static void Serialize<T>(T source, IniSection section) where T : class, new()
{
foreach (var propertyPair in GetPropertyPairs(typeof(T)))
{
var key = section.Keys.Add(propertyPair.Key);
if (key.ParentCollectionCore != null)
SetKeyValue(propertyPair.Value, source, key);
}
}
public static T Deserialize<T>(IniSection section) where T : class, new()
{
T destination = new T();
foreach (var propertyPair in GetPropertyPairs(typeof(T)))
{
var key = section.Keys[propertyPair.Key];
if (key != null)
SetPropertyValue(propertyPair.Value, destination, key);
}
return destination;
}
private static IEnumerable<KeyValuePair<string, PropertyInfo>> GetPropertyPairs(Type type)
{
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!IniSerializer.PropertyTypeVerifier(property.PropertyType))
continue;
string propertyName = null;
var attributes = property.GetCustomAttributes(typeof(IniSerializationAttribute), false);
if (attributes.Length > 0)
{
var attribute = (IniSerializationAttribute)attributes[0];
if (attribute.Ignore)
continue;
propertyName = attribute.Alias;
}
yield return new KeyValuePair<string, PropertyInfo>((propertyName) ?? property.Name, property);
}
}
private static void SetKeyValue(PropertyInfo property, object source, IniKey key)
{
object propertyValue = property.GetValue(source, null);
if (propertyValue == null)
return;
if (property.PropertyType.IsArray || property.PropertyType.GetInterface(typeof(IList).Name) != null)
{
var values = new List<string>();
/* MZ(2016-01-02): Fixed issue with null items in array and list. */
foreach (var item in (IEnumerable)propertyValue)
values.Add(item != null ? item.ToString() : null);
key.Values = values.ToArray();
}
else
key.Value = propertyValue.ToString();
}
private static void SetPropertyValue(PropertyInfo property, object destination, IniKey key)
{
var propertyType = property.PropertyType;
if (propertyType.IsArray)
{
/* MZ(2016-01-02): Fixed issue with null array and list. */
if (!key.IsValueArray)
return;
var values = key.Values;
var itemType = propertyType.GetElementType();
var array = Array.CreateInstance(itemType, values.Length);
for (int i = 0; i < values.Length; i++)
array.SetValue(
TypeDescriptor.GetConverter(itemType).ConvertFromInvariantString(values[i]),
i);
property.SetValue(destination, array, null);
}
else if (propertyType.GetInterface(typeof(IList).Name) != null)
{
/* MZ(2016-01-02): Fixed issue with null array and list. */
if (!key.IsValueArray)
return;
var itemType = propertyType.GetGenericArguments()[0];
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(itemType));
var values = key.Values;
if (!(values.Length == 1 && string.IsNullOrEmpty(values[0])))
foreach (var value in values)
list.Add(
TypeDescriptor.GetConverter(itemType).ConvertFromInvariantString(value));
property.SetValue(destination, list, null);
}
else
property.SetValue(
destination,
TypeDescriptor.GetConverter(propertyType).ConvertFromInvariantString(key.Value),
null);
}
}
}

View File

@ -0,0 +1,194 @@
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace MadMilkman.Ini
{
/// <summary>
/// Represents a class that is used for binding operations, an operation in which the <see cref="IniKey">placeholder keys</see> values are replaced with an internal or external data.
/// </summary>
/// <remarks>
/// <para><see cref="IniValueBinding"/> can be accessed through <see cref="IniFile.ValueBinding"/> property.</para>
/// <para>Binding can be executed with internal data source or with a provided external data source.</para>
/// <para>For more information see <see href="c49dc3a5-866f-4d2d-8f89-db303aceb5fe.htm#binding" target="_self">IniKey's Value Binding</see>.</para>
/// </remarks>
/// <seealso href="c49dc3a5-866f-4d2d-8f89-db303aceb5fe.htm#binding" target="_self">IniKey's Value Binding</seealso>
public sealed class IniValueBinding
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly IniValueBindingEventArgs args;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly IniFile iniFile;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private static readonly Regex placeholderPattern = new Regex(@"@\{[\w\s|]+\}", RegexOptions.Compiled | RegexOptions.CultureInvariant);
/// <summary>
/// Occurs when a placeholder is binding with data source value and can be used to customize the binding operation.
/// </summary>
public event EventHandler<IniValueBindingEventArgs> Binding;
internal IniValueBinding(IniFile iniFile)
{
if (iniFile == null)
throw new ArgumentNullException("iniFile");
this.iniFile = iniFile;
this.args = new IniValueBindingEventArgs();
}
/// <summary>
/// Executes a binding operation with internal data source.
/// </summary>
/// <seealso href="c49dc3a5-866f-4d2d-8f89-db303aceb5fe.htm#binding" target="_self">IniKey's Value Binding</seealso>
public void Bind()
{
foreach (var placeholderPair in this.GetPlaceholderPairs(null))
{
IniKey placeholderKey = placeholderPair.Key;
string placeholder = placeholderPair.Value;
string placeholderName = placeholder.Substring(2, placeholder.Length - 3);
string targetedValue;
int separator = placeholder.IndexOf('|');
if (separator != -1)
{
var targetedSection = this.iniFile.Sections[placeholder.Substring(2, separator - 2)];
if (targetedSection == null)
continue;
targetedValue = GetTargetedValue(
targetedSection,
placeholder.Substring(separator + 1, placeholder.Length - separator - 2));
}
else
targetedValue = GetTargetedValue(
placeholderKey.ParentSection,
placeholderName);
this.ExecuteBinding(placeholder, placeholderName, placeholderKey, targetedValue);
}
}
/// <summary>
/// Executes a binding operation with external data source.
/// </summary>
/// <param name="dataSource">The binding data source.</param>
/// <seealso href="c49dc3a5-866f-4d2d-8f89-db303aceb5fe.htm#binding" target="_self">IniKey's Value Binding</seealso>
public void Bind(object dataSource) { this.Bind(dataSource, null); }
/// <summary>
/// Executes a binding operation with external data source, only on specified section.
/// </summary>
/// <param name="dataSource">The binding data source.</param>
/// <param name="sectionName">The <see cref="IniSection"/>'s name.</param>
/// <seealso href="c49dc3a5-866f-4d2d-8f89-db303aceb5fe.htm#binding" target="_self">IniKey's Value Binding</seealso>
public void Bind(object dataSource, string sectionName)
{
if (dataSource == null)
throw new ArgumentNullException("dataSource");
var dataSourceDictionary = CreateDataSourceDictionary(dataSource);
if (dataSourceDictionary == null)
return;
foreach (var placeholderPair in this.GetPlaceholderPairs(this.iniFile.Sections[sectionName]))
{
IniKey placeholderKey = placeholderPair.Key;
string placeholder = placeholderPair.Value;
string placeholderName = placeholder.Substring(2, placeholder.Length - 3);
string targetedValue;
dataSourceDictionary.TryGetValue(placeholderName, out targetedValue);
this.ExecuteBinding(placeholder, placeholderName, placeholderKey, targetedValue);
}
}
private void ExecuteBinding(string placeholder, string placeholderName, IniKey placeholderKey, string targetedValue)
{
this.args.Initialize(placeholderName, placeholderKey, targetedValue, targetedValue != null);
if (this.Binding != null)
this.Binding(this, this.args);
if (this.args.Value != null)
placeholderKey.Value = placeholderKey.Value.Replace(placeholder, this.args.Value);
this.args.Reset();
}
// Returns placeholder pairs as KeyValuePair<IniKey, string>:
// Key = IniKey in which value's the placeholder resides
// Value = Placeholder, e.g. @{Placeholder}
private IEnumerable<KeyValuePair<IniKey, string>> GetPlaceholderPairs(IniSection section)
{
if (section != null)
{
foreach (IniKey key in section.Keys)
if (key.Value != null)
{
int matchStartat = key.Value.IndexOf("@{");
if (matchStartat != -1)
foreach (Match match in placeholderPattern.Matches(key.Value, matchStartat))
yield return new KeyValuePair<IniKey, string>(key, match.Value);
}
}
else
{
foreach (IniSection iniSection in this.iniFile.Sections)
foreach (var placeholderPair in this.GetPlaceholderPairs(iniSection))
yield return placeholderPair;
}
}
private static string GetTargetedValue(IniSection targetedSection, string targetedKeyName)
{
IniKey targetedKey = targetedSection.Keys[targetedKeyName];
return targetedKey != null ? targetedKey.Value : null;
}
/* REMARKS: TODO re-factor this, use providers ...
*
* CONSIDER: Implement support for any custom (including anonymous) types, use reflation ... */
private static IDictionary<string, string> CreateDataSourceDictionary(object dataSource)
{
var dictionary = dataSource as IDictionary<string, string>;
if (dictionary != null)
return dictionary;
var collection = dataSource as ICollection<KeyValuePair<string, string>>;
if (collection != null)
{
dictionary = new Dictionary<string, string>(collection.Count);
foreach (var dataSourceItem in collection)
if (!dictionary.ContainsKey(dataSourceItem.Key))
dictionary.Add(dataSourceItem);
return dictionary;
}
var enumerator = dataSource as IEnumerable<KeyValuePair<string, string>>;
if (enumerator != null)
{
dictionary = new Dictionary<string, string>();
foreach (var dataSourceItem in enumerator)
if (!dictionary.ContainsKey(dataSourceItem.Key))
dictionary.Add(dataSourceItem);
return dictionary;
}
if (dataSource is KeyValuePair<string, string>)
{
dictionary = new Dictionary<string, string>(1);
dictionary.Add((KeyValuePair<string, string>)dataSource);
return dictionary;
}
return null;
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.Diagnostics;
namespace MadMilkman.Ini
{
/// <summary>
/// Provides data for <see cref="IniValueBinding.Binding"/> event.
/// </summary>
public sealed class IniValueBindingEventArgs : EventArgs
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string placeholderName;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private IniKey placeholderKey;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private bool isValueFound;
/// <summary>
/// Gets the placeholder's name.
/// </summary>
public string PlaceholderName { get { return this.placeholderName; } }
/// <summary>
/// Gets the placeholder's <see cref="IniKey"/>.
/// </summary>
public IniKey PlaceholderKey { get { return this.placeholderKey; } }
/// <summary>
/// Gets or sets the data source value that will replace the placeholder.
/// </summary>
/// <value>
/// The data source value that will replace the placeholder, if it's not <see langword="null"/>.
/// </value>
public string Value { get; set; }
/// <summary>
/// Gets a value indicating whether value was found in the data source.
/// </summary>
/// <value>
/// <see langword="true"/> if value was found in the data source.
/// </value>
public bool IsValueFound { get { return this.isValueFound; } }
internal IniValueBindingEventArgs() { }
internal void Initialize(string placeholderName, IniKey placeholderKey, string value, bool isValueFound)
{
this.placeholderName = placeholderName;
this.placeholderKey = placeholderKey;
this.Value = value;
this.isValueFound = isValueFound;
}
internal void Reset() { this.Initialize(null, null, null, false); }
}
}

View File

@ -0,0 +1,77 @@
using System;
using System.Diagnostics;
using System.Collections.Generic;
namespace MadMilkman.Ini
{
/// <summary>
/// Represents a class of mapped <see cref="IniKey.Value"/>s and their results, used in <see cref="O:MadMilkman.Ini.IniKey.TryParseValue"/> methods.
/// </summary>
/// <remarks>
/// <para><see cref="IniValueMappings"/> can be accessed through <see cref="IniFile.ValueMappings"/> property.</para>
/// <para>Mapped value results have priority over parsing the value.</para>
/// <para>For more information see <see href="c49dc3a5-866f-4d2d-8f89-db303aceb5fe.htm#parsing" target="_self">IniKey's Value Parsing</see>.</para>
/// </remarks>
/// <seealso href="c49dc3a5-866f-4d2d-8f89-db303aceb5fe.htm#parsing" target="_self">IniKey's Value Parsing</seealso>
public sealed class IniValueMappings
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private static readonly Predicate<Type> MappedTypeVerifier = IniKey.IsSupportedValueType;
private readonly IDictionary<string, object> mappings;
internal IniValueMappings() { this.mappings = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); }
/// <summary>
/// Adds a new mapping of <see cref="IniKey.Value"/> to resulting object of parse methods.
/// </summary>
/// <param name="value">The key's value.</param>
/// <param name="mappedResult">The object that represents parsed <see cref="IniKey.Value"/>.</param>
/// <typeparam name="T">Type of the object that represents parsed <see cref="IniKey.Value"/>.</typeparam>
/// <remarks>
/// <para>The key's value cannot be <see langword="null"/>.</para>
/// <para>The mapped result's type must be one of the supported types for parsing, see the remarks of <see cref="IniKey.IsSupportedValueType(Type)"/> method.</para>
/// <para>Collection cannot contain multiple entries of same key's value, value comparison is case-insensitive.</para>
/// </remarks>
public void Add<T>(string value, T mappedResult)
{
if (value == null)
throw new ArgumentNullException("value");
if (this.Contains(value) || !IniValueMappings.MappedTypeVerifier(typeof(T)))
throw new InvalidOperationException();
this.mappings.Add(value, mappedResult);
}
/// <summary>
/// Determines whether the collection contains a mapping for a specified key's value.
/// </summary>
/// <param name="value">The key's value to locate in the collection.</param>
/// <returns><see langword="true"/> if the collection contains a mapping for a specified key's value.</returns>
public bool Contains(string value) { return this.mappings.ContainsKey(value); }
/// <summary>
/// Removes a mapping for a specified key's value in the collection.
/// </summary>
/// <param name="value">The key's value to remove in the collection.</param>
/// <returns><see langword="true"/> if a mapping for a specified key's value is successfully found and removed.</returns>
public bool Remove(string value) { return this.mappings.Remove(value); }
internal bool TryGetResult<T>(string value, out T result)
{
if (!string.IsNullOrEmpty(value))
{
object mappedResult;
if (this.mappings.TryGetValue(value, out mappedResult) &&
mappedResult.GetType() == typeof(T))
{
result = (T)mappedResult;
return true;
}
}
result = default(T);
return false;
}
}
}

View File

@ -0,0 +1,67 @@
using System;
namespace MadMilkman.Ini
{
delegate bool TryParseDelegate<TDelegate>(string value, out TDelegate result);
internal static class IniValueParser<T>
{
private static TryParseDelegate<T> parser;
static IniValueParser() { InitializeParser(typeof(T)); }
private static void InitializeParser(Type type)
{
if (type.IsEnum) SetParser<T>(EnumTryParse);
else if (type == typeof(TimeSpan)) SetParser<TimeSpan>(TimeSpan.TryParse);
else
switch (Type.GetTypeCode(type))
{
case TypeCode.Boolean: SetParser<bool>(bool.TryParse); break;
case TypeCode.Byte: SetParser<byte>(byte.TryParse); break;
case TypeCode.SByte: SetParser<sbyte>(sbyte.TryParse); break;
case TypeCode.Int16: SetParser<short>(short.TryParse); break;
case TypeCode.UInt16: SetParser<ushort>(ushort.TryParse); break;
case TypeCode.Int32: SetParser<int>(int.TryParse); break;
case TypeCode.UInt32: SetParser<uint>(uint.TryParse); break;
case TypeCode.Int64: SetParser<long>(long.TryParse); break;
case TypeCode.UInt64: SetParser<ulong>(ulong.TryParse); break;
case TypeCode.Single: SetParser<float>(float.TryParse); break;
case TypeCode.Double: SetParser<double>(double.TryParse); break;
case TypeCode.Decimal: SetParser<decimal>(decimal.TryParse); break;
case TypeCode.Char: SetParser<char>(char.TryParse); break;
case TypeCode.DateTime: SetParser<DateTime>(DateTime.TryParse); break;
case TypeCode.String: SetParser<string>((string value, out string result) => { result = value; return true; }); break;
}
}
private static void SetParser<TParser>(TryParseDelegate<TParser> parser)
{
IniValueParser<TParser>.parser = parser;
}
private static bool EnumTryParse<TEnum>(string value, out TEnum result)
{
/* REMARKS: To support case insensitivity instead of Enum.IsDefined use Enum.GetNames
* to achieve case insensitive string comparison between enum's names and value. */
Type type = typeof(TEnum);
if (Enum.IsDefined((type), value))
{
result = (TEnum)Enum.Parse(type, value);
return true;
}
result = default(TEnum);
return false;
}
public static bool TryParse(string value, out T result)
{
if (parser == null)
throw new NotSupportedException();
return parser(value, out result);
}
}
}