Перенос SchemaType-классов из PHP (пока для общих объектов)

This commit is contained in:
2025-03-27 17:23:52 +04:00
parent 4efb351980
commit e2f740c29d
8 changed files with 527 additions and 0 deletions
+4
View File
@@ -7,4 +7,8 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
+1
View File
@@ -1,2 +1,3 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.SchemaBuilder
{
internal abstract class AbstractSchemaType
{
/// <summary>
/// If the value is nullable.
/// </summary>
public bool IsNullable = false;
/// <summary>
/// Mark the value as nullable.
/// </summary>
/// <returns>This object</returns>
public AbstractSchemaType MarkNullable()
{
IsNullable = true;
return this;
}
/// <summary>
/// Check if type is compatible with current type.
/// </summary>
/// <param name="value">Value to be checked</param>
/// <returns>True if value is compatible with current type</returns>
public abstract bool IsCompatibleValue(object value);
/// <summary>
/// Adapt to new value.
/// If value is compatible with current type, `this` will be returned.
/// If value is not compatible, new type will be created.
/// </summary>
/// <param name="value">Value to be checked</param>
/// <returns>Compatible type</returns>
public virtual AbstractSchemaType Adapt(object value)
{
if (IsCompatibleValue(value))
{
return this;
}
return null;
}
/// <summary>
/// Add new possible value.
/// </summary>
/// <param name="value">New value</param>
public abstract void AddValue(object value);
/// <summary>
/// String representation of type.
/// </summary>
/// <returns></returns>
public abstract string GetTypeName();
public override abstract string ToString();
/// <summary>
/// Tabulation characters.
/// </summary>
public const string TAB = " ";
/// <summary>
/// Limit of elements in list or dictionary to be displayed.
/// </summary>
public const int ElementLimit = 20;
/// <summary>
/// Indent text.
/// </summary>
/// <param name="text">Text to be indented</param>
/// <returns>Indented text</returns>
public static string Indent(string text)
{
return text.Replace("\n", "\n" + TAB);
}
/// <summary>
/// Get string representation of value type.
/// </summary>
/// <param name="value">Value to be checked</param>
/// <returns>String representation of value type</returns>
public static string ResolveType(object value)
{
if (value == null)
{
return "null";
}
if (value is string)
{
return "string";
}
if (value is bool)
{
return "bool";
}
if (value is int)
{
return "int";
}
if (value is double)
{
return "double";
}
if (value is List<object>)
{
return "List<object>";
}
if (value is Dictionary<string, object>)
{
return "Dictionary<string, object>";
}
return "object";
}
/// <summary>
/// Create new type from value.
/// </summary>
/// <param name="value">Value to be checked</param>
/// <returns></returns>
public static AbstractSchemaType CreateFromValue(object value)
{
switch (ResolveType(value))
{
//case "null": return new NullSchemaType();
default: throw new NotImplementedException();
}
}
}
}
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.SchemaBuilder
{
internal class ArraySchemaType : AbstractSchemaType
{
public int MinCount = int.MaxValue;
public int MaxCount = int.MinValue;
protected AbstractSchemaType ElementType = new EmptySchemaType();
protected List<object> Values = [];
public override void AddValue(object value)
{
MinCount = Math.Min(MinCount, Values.Count);
MaxCount = Math.Max(MaxCount, Values.Count);
foreach (var item in Values)
{
ElementType.AddValue(item);
}
}
public override string GetTypeName()
{
throw new NotImplementedException();
}
public override bool IsCompatibleValue(object value)
{
var type = ResolveType(value);
if (type == "none")
{
return true;
}
if (type == "array")
{
return false;
}
foreach (var item in Values)
{
if (!ElementType.IsCompatibleValue(item))
{
return false;
}
}
return true;
}
public override AbstractSchemaType Adapt(object value)
{
if (value is not Array)
{
throw new Exception("Value is not an array");
}
var array = (object[])value;
if ((ResolveType(value) == "none") && (ElementType is EmptySchemaType))
{
ElementType = CreateFromValue(array[0]);
}
if (IsCompatibleValue(value))
{
foreach (var item in array)
{
ElementType.Adapt(item);
}
return this;
}
return new MixedSchemaType(this);
}
public override string ToString()
{
List<string> rows = [ GetTypeName() ];
rows.Add($"Length: min={MinCount} max={MaxCount}");
rows.Add("Value type:" + Indent(ElementType.GetTypeName()));
return string.Join("\n" + TAB, rows);
}
}
}
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.SchemaBuilder
{
internal class EmptySchemaType : AbstractSchemaType
{
public override AbstractSchemaType Adapt(object value)
{
if (IsCompatibleValue(value))
{
return this;
}
return CreateFromValue(value).MarkNullable();
}
public override void AddValue(object value)
{
}
public override string GetTypeName()
{
return "empty[] -> null";
}
public override bool IsCompatibleValue(object value)
{
return ResolveType(value) == "null";
}
public override string ToString()
{
return "null";
}
}
}
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.SchemaBuilder
{
internal class MixedSchemaType : AbstractSchemaType
{
public List<AbstractSchemaType> Types = [];
public MixedSchemaType(AbstractSchemaType baseType)
{
Types.Add(baseType);
}
public AbstractSchemaType AddType(object value)
{
var newType = CreateFromValue(value);
Types.Add(newType);
return newType;
}
public AbstractSchemaType? GetCompatibleType(object value)
{
foreach (var type in Types)
{
if (type.IsCompatibleValue(value))
{
return type;
}
}
return null;
}
public override AbstractSchemaType Adapt(object value)
{
var type = GetCompatibleType(value);
if (type == null)
{
AddType(value);
}
return this;
}
public override void AddValue(object value)
{
GetCompatibleType(value)?.AddValue(value);
}
public override string GetTypeName()
{
List<string> typeNames = Types.Select(t => t.GetTypeName()).ToList();
return string.Join("|", typeNames);
}
public override bool IsCompatibleValue(object value) => true;
/// <summary>
/// String representation of type.
/// </summary>
/// <returns></returns>
public override string ToString()
{
List<string> result = ["mixed"];
foreach (var type in Types)
{
result.Add(Indent(type?.ToString() ?? ""));
}
return string.Join("\n" + TAB, result);
}
}
}
@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.SchemaBuilder
{
internal class ObjectSchemaType : AbstractSchemaType
{
protected bool IsFresh = true;
public Dictionary<string, AbstractSchemaType> Fields = [];
public override void AddValue(object value)
{
if (value == null)
{
MarkNullable();
return;
}
var dict = (Dictionary<string, object>)value;
if (dict.Keys.Count == 0)
{
MarkNullable();
return;
}
foreach (var field in Fields)
{
if (!dict.ContainsKey(field.Key))
{
field.Value.MarkNullable();
}
}
foreach (var field in dict)
{
Fields[field.Key].AddValue(field.Value);
}
}
public override string GetTypeName()
{
throw new NotImplementedException();
}
public override bool IsCompatibleValue(object value)
{
return (ResolveType(value) == "object") || (ResolveType(value) == "null");
}
public override AbstractSchemaType Adapt(object value)
{
if (!IsCompatibleValue(value))
{
return new MixedSchemaType(this);
}
Dictionary<string, object> dict = (Dictionary<string, object>)value;
foreach (var field in Fields)
{
if (!dict.ContainsKey(field.Key))
{
field.Value.MarkNullable();
}
}
foreach (var field in dict) {
if (!Fields.ContainsKey(field.Key))
{
Fields[field.Key] = CreateFromValue(field.Value);
if (!IsFresh)
{
Fields[field.Key].MarkNullable();
}
}
}
IsFresh = false;
return this;
}
public override string ToString()
{
List<string> rows = ["object"];
foreach (var field in Fields)
{
string s = field.Key;
if (field.Value.IsNullable)
{
s = $"[{s}]";
}
s += " : " + Indent(field.Value.ToString());
rows.Add(Indent($"{field.Key}: {field.Value}"));
}
return string.Join("\n", rows);
}
}
}
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pf2eDataScrapper.SchemaBuilder
{
internal class StaticSchemaType(string type) : AbstractSchemaType
{
protected string Type = type;
protected Dictionary<object, int> Values = [];
public override void AddValue(object value)
{
Values.TryAdd(value, 0);
Values[value]++;
}
public override string GetTypeName()
{
return Type;
}
public override bool IsCompatibleValue(object value)
{
return ResolveType(value) == Type;
}
public override string ToString()
{
string result = Type;
int count = Values.Count;
result += $" (different values: {count})";
if ((count <= ElementLimit) && (count > 0))
{
result += "\n";
foreach (var value in Values)
{
result += $"{TAB}{value.Key} (x{value.Value})\n";
}
}
return result.Trim();
}
}
}