diff --git a/src/Generator.cs b/src/Generator.cs
deleted file mode 100644
index 009be0d..0000000
--- a/src/Generator.cs
+++ /dev/null
@@ -1,967 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net.Http;
-using System.Text;
-using System.Text.Json;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-
-namespace WTelegram
-{
- public class Generator
- {
- readonly Dictionary ctorToTypes = new();
- readonly HashSet allTypes = new();
- readonly Dictionary knownStyles = new() { ["InitConnection"] = 1, ["Help_GetConfig"] = 0, ["HttpWait"] = -1 };
- readonly Dictionary typeInfos = new();
- readonly HashSet enumTypes = new();
- readonly Dictionary enumValues = new();
- readonly HashSet nullableCtor = new();
- int currentLayer;
- string tabIndent;
- private string currentJson;
-
- public async Task FromWeb()
- {
- Console.WriteLine("Fetch web pages...");
-#if DEBUG
- currentLayer = await Task.FromResult(TL.Layer.Version);
-#else
- using var http = new HttpClient();
- //var html = await http.GetStringAsync("https://core.telegram.org/api/layers");
- //currentLayer = int.Parse(Regex.Match(html, @"#layer-(\d+)").Groups[1].Value);
- //File.WriteAllBytes("TL.MTProto.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/mtproto-json"));
- //File.WriteAllBytes("TL.Schema.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/json"));
- File.WriteAllBytes("TL.MTProto.tl", await http.GetByteArrayAsync("https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/Telegram/Resources/tl/mtproto.tl"));
- File.WriteAllBytes("TL.Schema.tl", await http.GetByteArrayAsync("https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/Telegram/Resources/tl/api.tl"));
- File.WriteAllBytes("TL.Secret.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/end-to-end-json"));
-#endif
- //FromJson("TL.MTProto.json", "TL.MTProto.cs", @"TL.Table.cs");
- //FromJson("TL.Schema.json", "TL.Schema.cs", @"TL.Table.cs");
- FromTL("TL.MTProto.tl", "TL.MTProto.cs");
- FromTL("TL.Schema.tl", "TL.Schema.cs");
- FromJson("TL.Secret.json", "TL.Secret.cs");
- }
-
- private void FromTL(string tlPath, string outputCs)
- {
- using var sr = new StreamReader(tlPath);
- var schema = new SchemaJson { constructors = new(), methods = new() };
- string line;
- bool inFunctions = false;
- while ((line = sr.ReadLine()) != null)
- {
- line = line.Trim();
- if (line == "---functions---")
- inFunctions = true;
- else if (line == "---types---")
- inFunctions = false;
- else if (line.StartsWith("// LAYER "))
- currentLayer = int.Parse(line[9..]);
- else if (line != "" && !line.StartsWith("//"))
- {
- if (!line.EndsWith(";")) System.Diagnostics.Debugger.Break();
- var words = line.Split(' ');
- int hash = words[0].IndexOf('#');
- if (hash == -1) { Console.WriteLine(line); continue; }
- if (words[^2] != "=") { Console.WriteLine(line); continue; }
- string name = words[0][0..hash];
- int id = int.Parse(words[0][(hash + 1)..], System.Globalization.NumberStyles.HexNumber);
- string type = words[^1].TrimEnd(';');
- var @params = words[1..^2].Where(word => word != "{X:Type}").Select(word =>
- {
- int colon = word.IndexOf(':');
- string name = word[0..colon];
- string type = word[(colon + 1)..];
- if (type == "string" && outputCs == "TL.MTProto.cs" && !name.Contains("message")) type = "bytes";
- return new Param { name = name, type = type };
- }).ToArray();
- if (inFunctions)
- schema.methods.Add(new Method { id = id.ToString(), method = name, type = type, @params = @params });
- else
- schema.constructors.Add(new Constructor { id = id.ToString(), predicate = name, type = type, @params = @params });
- }
- }
- FromSchema(schema, outputCs);
- }
-
- public void FromJson(string jsonPath, string outputCs)
- {
- Console.WriteLine("Parsing " + jsonPath);
- var schema = JsonSerializer.Deserialize(File.ReadAllText(jsonPath));
- FromSchema(schema, outputCs);
- }
-
- internal void FromSchema(SchemaJson schema, string outputCs)
- {
- currentJson = Path.GetFileNameWithoutExtension(outputCs);
- using var sw = new StreamWriter(outputCs, false, Encoding.UTF8);
- sw.WriteLine("// This file is generated automatically using the Generator class");
- sw.WriteLine("using System;");
- sw.WriteLine("using System.Collections.Generic;");
- if (schema.methods.Count != 0) sw.WriteLine("using System.Threading.Tasks;");
- sw.WriteLine();
- sw.WriteLine("namespace TL");
- sw.WriteLine("{");
- sw.WriteLine("\tusing BinaryWriter = System.IO.BinaryWriter;");
- sw.WriteLine("\tusing Client = WTelegram.Client;");
- tabIndent = "\t";
- foreach (var ctor in schema.constructors)
- {
- if (ctorToTypes.ContainsKey(ctor.ID)) continue;
- if (ctor.type == "Vector t") continue;
- var structName = CSharpName(ctor.predicate);
- ctorToTypes[ctor.ID] = ctor.layer == 0 ? structName : $"Layer{ctor.layer}.{structName}";
- var typeInfo = typeInfos.GetOrCreate(ctor.type);
- if (ctor.ID == 0x5BB8E511) { ctorToTypes[ctor.ID] = structName = ctor.predicate = ctor.type = "_Message"; }
- else if (ctor.ID == TL.Layer.NullCtor) { ctorToTypes[ctor.ID] += "=null"; typeInfo.Nullable = ctor; nullableCtor.Add(ctor.predicate); }
- if (typeInfo.ReturnName == null) typeInfo.ReturnName = CSharpName(ctor.type);
- typeInfo.Structs.Add(ctor);
- if (structName == typeInfo.ReturnName) typeInfo.MainClass = ctor;
- }
- foreach (var (name, typeInfo) in typeInfos)
- {
- if (allTypes.Contains(typeInfo.ReturnName))
- {
- if (typeInfos.TryGetValue(typeInfo.ReturnName, out var existingType))
- {
- typeInfo.ReturnName = existingType.ReturnName;
- typeInfo.MainClass = existingType.MainClass;
- }
- continue;
- }
-
- if (typeInfo.Structs.All(ctor => ctor.@params.Length == 0))
- typeInfo.AsEnum = true;
- var nullable = typeInfo.Structs.Where(c => c.predicate == "help.noAppUpdate" ||
- c.predicate.EndsWith("Empty") || c.predicate.EndsWith("Unknown") || c.predicate.EndsWith("NotModified")).ToList();
- if (nullable.Count == 1 && nullable[0].@params.Length == 0 && !typeInfo.AsEnum)
- {
- typeInfo.Nullable = nullable[0];
- typeInfo.Structs.Remove(typeInfo.Nullable);
- ctorToTypes[typeInfo.Nullable.ID] += "=null";
- nullableCtor.Add(typeInfo.Nullable.predicate);
- }
- if (typeInfo.MainClass == null)
- {
- List fakeCtorParams = new();
- if (typeInfo.Structs.Count > 1)
- {
- while (typeInfo.Structs[0].@params.Length > fakeCtorParams.Count)
- {
- fakeCtorParams.Add(typeInfo.Structs[0].@params[fakeCtorParams.Count]);
- if (!typeInfo.Structs.All(ctor => HasPrefix(ctor, fakeCtorParams)))
- {
- fakeCtorParams.RemoveAt(fakeCtorParams.Count - 1);
- break;
- }
- }
- if (fakeCtorParams.Count == 0)
- while (typeInfo.Structs[0].@params.Length > fakeCtorParams.Count)
- {
- fakeCtorParams.Insert(0, typeInfo.Structs[0].@params[^(fakeCtorParams.Count + 1)]);
- if (!typeInfo.Structs.All(ctor => HasSuffix(ctor, fakeCtorParams)))
- {
- fakeCtorParams.RemoveAt(0);
- break;
- }
- }
- }
- typeInfo.MainClass = new Constructor { id = null, @params = fakeCtorParams.ToArray(), predicate = typeInfo.ReturnName, type = name };
- typeInfo.Structs.Insert(0, typeInfo.MainClass);
- typeInfo.CommonFields = fakeCtorParams.Count; // generation of abstract main class with some common fields
- }
- else if (typeInfo.Structs.Count > 1)
- {
- if (typeInfo.Structs.All(ctor => ctor == typeInfo.MainClass || HasPrefix(ctor, typeInfo.MainClass.@params) || HasSuffix(ctor, typeInfo.MainClass.@params)))
- typeInfo.CommonFields = typeInfo.MainClass.@params.Length;
- else
- {
- // the previous MainClass (ctor have the same name as ReturnName) is incompatible with other classes fields
- typeInfo.MainClass = new Constructor { id = null, @params = Array.Empty(), predicate = typeInfo.ReturnName + "Base", type = name };
- typeInfo.Structs.Insert(0, typeInfo.MainClass);
- typeInfo.ReturnName = typeInfo.MainClass.predicate;
- }
- typeInfo.AbstractUserOrChat = AbstractUserOrChatTypes.Contains(typeInfo.ReturnName);
- if (typeInfo.CommonFields == 0)
- {
- var autoProps = typeInfo.Structs.OrderByDescending(s => s.@params.Length).First().@params
- .Where(p => !p.type.EndsWith("?true")).ToList();
- if (typeInfo.AbstractUserOrChat) { autoProps.Remove(ParamUsers); autoProps.Remove(ParamChats); }
- autoProps.Remove(ParamFlags);
- int autoPropsCount = 0;
- foreach (var str in typeInfo.Structs)
- {
- if (str.ID == 0 || str.predicate.EndsWith("Empty") || str.predicate.EndsWith("TooLong") || str.predicate.EndsWith("NotModified")) continue;
- for (int i = autoProps.Count - 1; i >= 0; i--)
- if (!str.@params.Contains(autoProps[i]))
- autoProps.RemoveAt(i);
- if (autoProps.Count == 0) break;
- ++autoPropsCount;
- }
- if (autoProps.Count > 0 && autoPropsCount > 1)
- typeInfo.AutoProps = autoProps;
- }
- }
- if (typeInfo.AsEnum && typeInfo.MainClass.id == null)
- {
- enumTypes.Add(typeInfo.ReturnName);
- bool lowercase = typeInfo.ReturnName == "Storage_FileType";
- string prefix = "";
- while ((prefix += typeInfo.Structs[1].predicate[prefix.Length]) != null)
- if (!typeInfo.Structs.All(ctor => ctor.id == null || ctor.predicate.StartsWith(prefix)))
- break;
- int prefixLen = CSharpName(prefix).Length - 1;
- foreach (var ctor in typeInfo.Structs)
- {
- if (ctor.id == null) continue;
- string className = CSharpName(ctor.predicate);
- if (!allTypes.Add(className)) continue;
- if (lowercase) className = className.ToLowerInvariant();
- enumValues.Add(ctor.predicate, $"{typeInfo.ReturnName}.{className[prefixLen..]}");
- ctorToTypes.Remove(ctor.ID);
- }
- }
- }
- var layers = schema.constructors.Select(c => c.layer).Distinct().ToList();
- if (layers.Count > 1) // multi-layer file => generate abstract classes out of layer namespaces first
- foreach (var typeInfo in typeInfos.Values)
- WriteTypeInfo(sw, typeInfo, 0);
- foreach (var layer in layers)
- {
- if (layer != 0)
- {
- sw.WriteLine();
- sw.WriteLine("\tnamespace Layer" + layer);
- sw.Write("\t{");
- tabIndent += "\t";
- }
- foreach (var typeInfo in typeInfos.Values)
- WriteTypeInfo(sw, typeInfo, layer);
- if (layer != 0)
- {
- sw.WriteLine("\t}");
- tabIndent = tabIndent[1..];
- }
- }
- if (typeInfos.GetValueOrDefault("Message")?.MainClass.ID == 0x5BB8E511) typeInfos.Remove("Message");
-
- if (schema.methods.Count != 0)
- {
- var ping = schema.methods.FirstOrDefault(m => m.method == "ping");
- if (ping != null)
- {
- var typeInfo = new TypeInfo { ReturnName = ping.type, MainClass =
- new Constructor { id = ping.id, @params = ping.@params, predicate = ping.method, type = ping.type } };
- typeInfo.Structs.Add(typeInfo.MainClass);
- ctorToTypes[int.Parse(ping.id)] = CSharpName(ping.method);
- WriteTypeInfo(sw, typeInfo, 0);
- }
- sw.WriteLine();
- sw.WriteLine("\t// ---functions---");
- sw.WriteLine();
- sw.WriteLine($"\tpublic static class {currentJson[3..]}");
- //sw.WriteLine("\tpublic static partial class Fn // ---functions---");
- sw.Write("\t{");
- tabIndent = "\t\t";
- foreach (var method in schema.methods)
- {
- WriteMethod(sw, method);
- //var typeInfo = new TypeInfo { ReturnName = method.type };
- //typeInfo.Structs.Add(new Constructor { id = method.id, @params = method.@params, predicate = method.method, type = method.type });
- //methods.Add(typeInfo);
- //WriteTypeInfo(sw, typeInfo, "", true);
- }
- sw.WriteLine("\t}");
- }
- sw.WriteLine("}");
-
- UpdateTable("TL.Table.cs");
- }
-
- void WriteTypeInfo(StreamWriter sw, TypeInfo typeInfo, int layer)
- {
- var genericType = typeInfo.ReturnName.Length == 1 ? $"<{typeInfo.ReturnName}>" : null;
- bool needNewLine = true;
- int commonFields = 0;
- foreach (var ctor in typeInfo.Structs)
- {
- if (ctor.layer != layer) continue;
- int ctorId = ctor.ID;
- string className = CSharpName(ctor.predicate) + genericType;
- if (!allTypes.Add((layer == 0 ? "" : $"Layer{layer}.") + className)) continue;
- if (needNewLine) { needNewLine = false; sw.WriteLine(); }
- var parentClass = ctor == typeInfo.MainClass ? "ITLObject" : typeInfo.ReturnName;
- var parms = ctor.@params;
- var webDoc = WriteXmlDoc(sw, typeInfo, ctor);
- if (ctorId == 0) // abstract parent
- {
- if (typeInfo.AsEnum)
- {
- WriteTypeAsEnum(sw, typeInfo, webDoc);
- return;
- }
- sw.Write($"{tabIndent}public abstract partial class {ctor.predicate}");
- if (webDoc != null && (parms.Length > 0 || typeInfo.AutoProps != null))
- {
- var len = Math.Max(parms.Length, typeInfo.AutoProps?.Count ?? 0);
- var webDoc2 = ParseWebDoc($"constructor/{typeInfo.Structs.Skip(1).First(s => s.@params.Length >= len).predicate}");
- webDoc["Parameters"] = webDoc2["Parameters"];
- }
- }
- else
- {
- string tldefReverse = null;
- if (commonFields != 0)
- {
- if (ctor.@params[0].name == typeInfo.MainClass.@params[0].name)
- parms = ctor.@params.Skip(commonFields).ToArray();
- else
- {
- parms = ctor.@params.Take(ctor.@params.Length - commonFields).ToArray();
- tldefReverse = ", inheritAfter = true";
- }
- }
- else
- {
- foreach (var other in typeInfo.Structs)
- {
- if (other == ctor) continue;
- var otherParams = other.@params;
- if (otherParams.Length <= commonFields) continue;
- if (!IsDerivedName(ctor.predicate, other.predicate)) continue;
- if (HasPrefix(ctor, otherParams))
- {
- parms = ctor.@params.Skip(otherParams.Length).ToArray();
- tldefReverse = null;
- }
- else if (HasSuffix(ctor, otherParams))
- {
- parms = ctor.@params.Take(ctor.@params.Length - otherParams.Length).ToArray();
- tldefReverse = ", inheritAfter = true";
- }
- else continue;
- commonFields = otherParams.Length;
- parentClass = CSharpName(other.predicate) + genericType;
- }
- }
- if (currentJson != "TL.MTProto")
- sw.WriteLine($"{tabIndent}[TLDef(0x{ctor.ID:X8}{tldefReverse})]");
- else
- {
- sw.Write($"{tabIndent}[TLDef(0x{ctor.ID:X8}{tldefReverse})] //{ctor.predicate}#{ctor.ID:x8} ");
- if (genericType != null) sw.Write($"{{{typeInfo.ReturnName}:Type}} ");
- foreach (var parm in ctor.@params) sw.Write($"{parm.name}:{parm.type} ");
- sw.WriteLine($"= {ctor.type}");
- }
- sw.Write($"{tabIndent}public partial class {className}");
- //sw.Write(skipParams == 0 && typeInfo.NeedAbstract > 0 ? "ITLObject" : parentClass);
- }
- sw.Write(" : ");
- sw.Write(parentClass);
- if (parms.Length == 0 && !typeInfo.AbstractUserOrChat && typeInfo.AutoProps == null)
- {
- sw.WriteLine(" { }");
- commonFields = typeInfo.CommonFields;
- continue;
- }
- var paramDoc = webDoc?.GetValueOrDefault("Parameters").table;
- var hasFlagEnum = parms.Any(p => p.type.StartsWith("flags."));
- sw.WriteLine();
- sw.WriteLine(tabIndent + "{");
- foreach (var parm in parms)
- {
- if (parm.type.EndsWith("?true")) continue;
- var doc = paramDoc?.GetValueOrDefault(parm.name);
- if (doc != null) sw.WriteLine($"{tabIndent}\t/// {doc}");
- if (parm.type == "#")
- sw.WriteLine($"{tabIndent}\tpublic {(hasFlagEnum ? "Flags" : "int")} {parm.name};");
- else
- {
- if (parm.type.StartsWith("flags."))
- {
- int qm = parm.type.IndexOf('?');
- sw.WriteLine($"{tabIndent}\t[IfFlag({parm.type[6..qm]})] public {MapType(parm.type[(qm + 1)..], parm.name)} {MapName(parm.name)};");
- }
- else
- sw.WriteLine($"{tabIndent}\tpublic {MapType(parm.type, parm.name)} {MapName(parm.name)};");
- }
- }
- if (hasFlagEnum)
- {
- sw.WriteLine();
- var list = new SortedList();
- var flagDoc = new Dictionary();
- foreach (var parm in parms)
- {
- if (!parm.type.StartsWith("flags.") || !parm.type.EndsWith("?true")) continue;
- var mask = 1 << int.Parse(parm.type[6..parm.type.IndexOf('?')]);
- if (list.ContainsKey(mask)) continue;
- var doc = paramDoc?.GetValueOrDefault(parm.name);
- if (doc != null) flagDoc[mask] = doc;
- list[mask] = MapName(parm.name);
- }
- foreach (var parm in parms)
- {
- if (!parm.type.StartsWith("flags.") || parm.type.EndsWith("?true")) continue;
- var mask = 1 << int.Parse(parm.type[6..parm.type.IndexOf('?')]);
- if (list.ContainsKey(mask)) continue;
- flagDoc[mask] = $"Field has a value";
- var name = MapName("has_" + parm.name);
- if (list.Values.Contains(name)) name += "_field";
- list[mask] = name;
- }
- sw.WriteLine(tabIndent + "\t[Flags] public enum Flags");
- sw.WriteLine(tabIndent + "\t{");
- foreach (var (mask, name) in list)
- {
- if (flagDoc.TryGetValue(mask, out var summary)) sw.WriteLine($"{tabIndent}\t\t/// {summary}");
- sw.WriteLine($"{tabIndent}\t\t{name} = 0x{mask:X},");
- }
- sw.WriteLine(tabIndent + "\t}");
- }
- if (typeInfo.AutoProps != null)
- {
- bool firstLine = parms.Length != 0 || hasFlagEnum;
- string format = $"{tabIndent}\tpublic ";
- if (ctorId == 0)
- format += "abstract {0} {1} {{ get; }}";
- else if (ctor == typeInfo.MainClass)
- format += "virtual {0} {1} => {2};";
- else
- format += "override {0} {1} => {2};";
- foreach (var parm in typeInfo.AutoProps)
- {
- var value = "default";
- if (ctor.@params.Any(p => p.name == parm.name))
- if (!parms.Any(p => p.name == parm.name)) continue;
- else value = MapName(parm.name);
- else if (parm.type.StartsWith("Vector<") && className.EndsWith("Empty"))
- value = $"Array.Empty<{MapType(parm.type, parm.name).TrimEnd('[', ']')}>()";
- string csName = CSharpName(parm.name);
- if (csName.EndsWith("Id") && parm.type != "int" && parm.type != "long") csName = csName[..^2];
- if (firstLine) { sw.WriteLine(); firstLine = false; }
- var doc = paramDoc?.GetValueOrDefault(parm.name);
- if (doc != null) sw.WriteLine($"{tabIndent}\t/// {doc}");
- sw.WriteLine(string.Format(format, MapType(parm.type, parm.name), csName, value));
- }
- }
- var hasUsersChats = parms.Contains(ParamUsers) && parms.Contains(ParamChats);
- if (hasUsersChats || (typeInfo.AbstractUserOrChat && (ctor == typeInfo.MainClass || parentClass == typeInfo.ReturnName)))
- {
- var modifier = !typeInfo.AbstractUserOrChat ? null : ctorId == 0 ? "abstract " : "override ";
- bool withArg = !hasUsersChats || ctor.@params.Length != 3 || !parms.Contains(ParamPeer);
- sw.WriteLine($"{tabIndent}\t/// returns a or for {(withArg ? "the given Peer" : "the result")}");
- sw.Write($"{tabIndent}\tpublic {modifier}IPeerInfo UserOrChat");
- if (withArg)
- sw.Write("(Peer peer)");
- if (modifier == "abstract ")
- sw.WriteLine(";");
- else if (hasUsersChats)
- sw.WriteLine(" => peer.UserOrChat(users, chats);");
- else
- sw.WriteLine(" => null;");
- }
-
- sw.WriteLine(tabIndent + "}");
- commonFields = typeInfo.CommonFields;
- }
- }
-
- private Dictionary table)> WriteXmlDoc(StreamWriter sw, TypeInfo typeInfo, Constructor ctor)
- {
- if (currentJson == "TL.MTProto") return null;
- var url = ctor.id == null ? $"type/{ctor.type}" : $"constructor/{ctor.predicate}";
- var webDoc = ParseWebDoc(url);
- var summary = webDoc?.GetValueOrDefault(ctor.id == null ? ctor.type : ctor.predicate).descr;
- var derived = webDoc?.GetValueOrDefault("Constructors").table;
- if (derived != null && !typeInfo.AsEnum)
- summary += $"\t\tDerived classes: {string.Join(", ", derived.Keys.Where(k => k != ""))}";
- summary += $"\t\tSee ";
- sw.WriteLine($"{tabIndent}/// {summary.Trim()}");
- if (typeInfo.Nullable != null && ctor == typeInfo.MainClass)
- sw.WriteLine($"{tabIndent}/// a null value means {typeInfo.Nullable.predicate}");
- return webDoc;
- }
-
- private void WriteXmlDoc(StreamWriter sw, Method method)
- {
- if (currentJson == "TL.MTProto") return;
- var webDoc = ParseWebDoc($"method/{method.method}");
- var summary = webDoc?.GetValueOrDefault(method.method).descr;
- var paramDoc = webDoc?.GetValueOrDefault("Parameters").table;
- var excepDoc = webDoc?.GetValueOrDefault("Possible errors").table;
- summary += $"\t\tSee ";
- sw.WriteLine($"{tabIndent}/// {summary.Trim()}");
- if (paramDoc != null)
- foreach (var (name, doc) in paramDoc)
- if (name != "flags")
- sw.WriteLine($"{tabIndent}/// {doc}");
- if (typeInfos.GetValueOrDefault(method.type)?.Nullable is Constructor nullable)
- sw.WriteLine($"{tabIndent}/// a null value means {nullable.predicate}");
- if (excepDoc != null)
- sw.WriteLine($"{tabIndent}/// Possible errors: {string.Join(",", new SortedSet(excepDoc.Keys))} (details)");
- }
-
- private Dictionary table)> ParseWebDoc(string url)
- {
- var path = $@"{Environment.GetEnvironmentVariable("telegram-crawler")}\data\corefork.telegram.org\{url}";
- if (!File.Exists(path))
- if (!File.Exists(path += ".html"))
- return null;
- var result = new Dictionary table)>();
- var html = File.ReadAllText(path);
- foreach (var section in html[html.IndexOf("" }, StringSplitOptions.None))
- {
- var index = 0;
- do { index = section.IndexOf('>', index) + 1; } while (section[index] == '<');
- var title = section[index..section.IndexOf("")) >= 0)
- {
- descr = rest[(index + 3)..rest.IndexOf("
", index)];
- rest = rest[(index + 7 + descr.Length)..].Trim();
- descr = ProcessDescr(descr);
- }
- Dictionary table = null;
- if (rest.StartsWith("