Перенос SchemaType-классов из PHP (пока для общих объектов)
This commit is contained in:
@@ -7,4 +7,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user