From e2f740c29d29b8896b122fa758eec7aa43d29506 Mon Sep 17 00:00:00 2001 From: kozhilya Date: Thu, 27 Mar 2025 17:23:52 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81=20Sc?= =?UTF-8?q?hemaType-=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=BE=D0=B2=20=D0=B8?= =?UTF-8?q?=D0=B7=20PHP=20(=D0=BF=D0=BE=D0=BA=D0=B0=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=BE=D0=B1=D1=89=D0=B8=D1=85=20=D0=BE=D0=B1=D1=8A=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D0=B2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pf2eDataScrapper/Pf2eDataScrapper.csproj | 4 + Pf2eDataScrapper/Program.cs | 1 + .../SchemaBuilder/AbstractSchemaType.cs | 142 ++++++++++++++++++ .../SchemaBuilder/ArraySchemaType.cs | 98 ++++++++++++ .../SchemaBuilder/EmptySchemaType.cs | 40 +++++ .../SchemaBuilder/MixedSchemaType.cs | 81 ++++++++++ .../SchemaBuilder/ObjectSchemaType.cs | 110 ++++++++++++++ .../SchemaBuilder/StaticSchemaType.cs | 51 +++++++ 8 files changed, 527 insertions(+) create mode 100644 Pf2eDataScrapper/SchemaBuilder/AbstractSchemaType.cs create mode 100644 Pf2eDataScrapper/SchemaBuilder/ArraySchemaType.cs create mode 100644 Pf2eDataScrapper/SchemaBuilder/EmptySchemaType.cs create mode 100644 Pf2eDataScrapper/SchemaBuilder/MixedSchemaType.cs create mode 100644 Pf2eDataScrapper/SchemaBuilder/ObjectSchemaType.cs create mode 100644 Pf2eDataScrapper/SchemaBuilder/StaticSchemaType.cs diff --git a/Pf2eDataScrapper/Pf2eDataScrapper.csproj b/Pf2eDataScrapper/Pf2eDataScrapper.csproj index 2150e37..af748c0 100644 --- a/Pf2eDataScrapper/Pf2eDataScrapper.csproj +++ b/Pf2eDataScrapper/Pf2eDataScrapper.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/Pf2eDataScrapper/Program.cs b/Pf2eDataScrapper/Program.cs index 3751555..3ed0e5d 100644 --- a/Pf2eDataScrapper/Program.cs +++ b/Pf2eDataScrapper/Program.cs @@ -1,2 +1,3 @@ // See https://aka.ms/new-console-template for more information Console.WriteLine("Hello, World!"); + diff --git a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaType.cs new file mode 100644 index 0000000..5b92be2 --- /dev/null +++ b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaType.cs @@ -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 + { + /// + /// If the value is nullable. + /// + public bool IsNullable = false; + + /// + /// Mark the value as nullable. + /// + /// This object + public AbstractSchemaType MarkNullable() + { + IsNullable = true; + return this; + } + + /// + /// Check if type is compatible with current type. + /// + /// Value to be checked + /// True if value is compatible with current type + public abstract bool IsCompatibleValue(object value); + + /// + /// 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. + /// + /// Value to be checked + /// Compatible type + public virtual AbstractSchemaType Adapt(object value) + { + if (IsCompatibleValue(value)) + { + return this; + } + + return null; + } + + /// + /// Add new possible value. + /// + /// New value + public abstract void AddValue(object value); + + /// + /// String representation of type. + /// + /// + public abstract string GetTypeName(); + + public override abstract string ToString(); + + + + /// + /// Tabulation characters. + /// + public const string TAB = " "; + + /// + /// Limit of elements in list or dictionary to be displayed. + /// + public const int ElementLimit = 20; + + /// + /// Indent text. + /// + /// Text to be indented + /// Indented text + public static string Indent(string text) + { + return text.Replace("\n", "\n" + TAB); + } + + /// + /// Get string representation of value type. + /// + /// Value to be checked + /// String representation of value type + 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) + { + return "List"; + } + if (value is Dictionary) + { + return "Dictionary"; + } + return "object"; + } + + /// + /// Create new type from value. + /// + /// Value to be checked + /// + public static AbstractSchemaType CreateFromValue(object value) + { + + switch (ResolveType(value)) + { + //case "null": return new NullSchemaType(); + + default: throw new NotImplementedException(); + } + } + } +} diff --git a/Pf2eDataScrapper/SchemaBuilder/ArraySchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/ArraySchemaType.cs new file mode 100644 index 0000000..af91b17 --- /dev/null +++ b/Pf2eDataScrapper/SchemaBuilder/ArraySchemaType.cs @@ -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 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 rows = [ GetTypeName() ]; + + rows.Add($"Length: min={MinCount} max={MaxCount}"); + rows.Add("Value type:" + Indent(ElementType.GetTypeName())); + + return string.Join("\n" + TAB, rows); + } + } +} diff --git a/Pf2eDataScrapper/SchemaBuilder/EmptySchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/EmptySchemaType.cs new file mode 100644 index 0000000..9e6676d --- /dev/null +++ b/Pf2eDataScrapper/SchemaBuilder/EmptySchemaType.cs @@ -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"; + } + } +} diff --git a/Pf2eDataScrapper/SchemaBuilder/MixedSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/MixedSchemaType.cs new file mode 100644 index 0000000..4c75c4d --- /dev/null +++ b/Pf2eDataScrapper/SchemaBuilder/MixedSchemaType.cs @@ -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 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 typeNames = Types.Select(t => t.GetTypeName()).ToList(); + + return string.Join("|", typeNames); + } + + public override bool IsCompatibleValue(object value) => true; + + /// + /// String representation of type. + /// + /// + public override string ToString() + { + List result = ["mixed"]; + + foreach (var type in Types) + { + result.Add(Indent(type?.ToString() ?? "")); + } + + return string.Join("\n" + TAB, result); + } + } +} diff --git a/Pf2eDataScrapper/SchemaBuilder/ObjectSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/ObjectSchemaType.cs new file mode 100644 index 0000000..6ac95b2 --- /dev/null +++ b/Pf2eDataScrapper/SchemaBuilder/ObjectSchemaType.cs @@ -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 Fields = []; + + public override void AddValue(object value) + { + if (value == null) + { + MarkNullable(); + return; + } + + var dict = (Dictionary)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 dict = (Dictionary)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 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); + } + } + +} diff --git a/Pf2eDataScrapper/SchemaBuilder/StaticSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/StaticSchemaType.cs new file mode 100644 index 0000000..b8a39c3 --- /dev/null +++ b/Pf2eDataScrapper/SchemaBuilder/StaticSchemaType.cs @@ -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 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(); + } + } +}