using System.Collections.Specialized; using System.Diagnostics; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using HtmlAgilityPack; using MewtocolNet; namespace AutoTools.ChmDataExtract; internal class Program { const string sysVarsLoc = @"Panasonic-ID SUNX Control\Control FPWIN Pro 7\Mak\Res_Eng\SysVars.chm"; const string funcNamesLoc = @"Panasonic-ID SUNX Control\Control FPWIN Pro 7\Mak\Res_Eng\FPWINPro.chm"; static Dictionary> plcGroups = new() { { "FP7 CPS41/31 E/ES", new List { PlcType.FP7_120k__CPS31E, PlcType.FP7_196k__CPS41E, PlcType.FP7_120k__CPS31ES, PlcType.FP7_196k__CPS41ES, }}, { "FP7 CPS31/31S", new List { PlcType.FP7_120k__CPS31, PlcType.FP7_120k__CPS31S, }}, { "FP7 CPS21", new List { PlcType.FP7_64k__CPS21, }}, { "ELC500", new List { PlcType.ECOLOGIX_0k__ELC500, }}, { "FP-SIGMA 12k", new List { PlcType.FPdSIGMA_12k, PlcType.FPdSIGMA_16k, }}, { "FP-SIGMA 32k", new List { PlcType.FPdSIGMA_32k, PlcType.FPdSIGMA_40k, }}, { "FP0R 16k/32k C types", new List { PlcType.FP0R_16k__C10_C14_C16, PlcType.FP0R_32k__C32, }}, { "FP0R 32k T32", new List { PlcType.FP0R_32k__T32, PlcType.FP0R_32k__F32, }}, { "FP2 16k", new List { PlcType.FP2_16k, }}, { "FP2 32k", new List { PlcType.FP2_32k, }}, { "FP2SH 32k/60k/120k", new List { PlcType.FP2SH_60k, PlcType.FP2SH_60k, PlcType.FP2SH_120k, }}, { "FP-X 16k/32k R-types", new List { PlcType.FPdX_16k__C14R, PlcType.FPdX_32k__C30R_C60R, }}, { "FP-X 16k/32k T-types", new List { PlcType.FPdX_16k__C14TsP, PlcType.FPdX_32k__C30TsP_C60TsP_C38AT_C40T, }}, {"FP0H C32T/P ET/EP", new List { PlcType.FP0H_32k__C32TsP, PlcType.FP0H_32k__C32ETsEP, }}, { "FP-X 16k/32k L-types", new List { PlcType.FPdX_16k__L14, PlcType.FPdX_32k__L30_L60, }}, { "FP-X 2.5k C40RT0A", new List { PlcType.FPdX_2c5k__C40RT0A, }}, { "FP-X0 2.5k L14,L30", new List { PlcType.FPdX0_2c5k__L14_L30, }}, { "FP-X0 8k L40,L60", new List { PlcType.FPdX0_8k__L40_L60, }}, { "FP-e 2.7k", new List { PlcType.FPde_2c7k, }}, { "FP-XH 16k/32k R-types", new List { PlcType.FPdXH_16k__C14R, PlcType.FPdXH_32k__C30R_C40R_C60R, }}, { "FP-XH 16k/32k T-types", new List { PlcType.FPdXH_16k__C14TsP, PlcType.FPdXH_32k__C30TsP_C40T_C60TsP, PlcType.FPdXH_32k__C30TsP_C40T_C60TsP, PlcType.FPdXH_32k__C38AT, PlcType.FPdXH_32k__C40ET_C60ET, PlcType.FPdXH_32k__C60ETF, }}, { "FP-XH M4/M8 types", new List { PlcType.FPdXH_32k__M4TsL, PlcType.FPdXH_32k__M8N16TsP, PlcType.FPdXH_32k__M8N30T, }}, }; class AddressException { public string ExceptionTitle; public string ForSysRegister; } internal class FPFunction { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string RedundantName { get; set; } = null!; public string Description { get; set; } = null!; } static void Main(string[] args) => Task.Run(AsyncMain).Wait(); static async Task AsyncMain () { GetFunctionNames(); //await GetSystemRegisters(); } static void GetFunctionNames () { var functions = new Dictionary(); var progLoc = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; var progFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); var sysVarsPath = Path.Combine(progFilesPath, funcNamesLoc); Directory.SetCurrentDirectory(progLoc); File.Copy(sysVarsPath, "./FPWINPro.chm", true); var startInfo = new ProcessStartInfo { WorkingDirectory = progLoc, FileName = "hh.exe", Arguments = $"-decompile ./DecompFuncs ./FPWINPro.chm", }; //call the hh.exe decompiler for chm if (!File.Exists("./DecompFuncs/topics/availability.html")) { var proc = Process.Start(startInfo)!; proc.WaitForExit(); } var doc = new HtmlDocument(); doc.Load("./DecompFuncs/topics/availability.html"); //[contains(@class, 'table mainbody')] foreach (HtmlNode table in doc.DocumentNode.SelectNodes("//table[1]")) { var rows = table?.SelectSingleNode("tbody")?.SelectNodes("tr"); if (rows == null) continue; foreach (var row in rows) { var columns = row.SelectNodes("td"); if (columns == null) continue; var itemRow = columns?.FirstOrDefault()?.SelectSingleNode("p/a[contains(@class,'xref')]"); string rowName = itemRow?.InnerText ?? "Unnamed"; if (!Regex.IsMatch(rowName, @"^F[0-9]{1,3}_.*$")) continue; FPFunction functionIns = new FPFunction(); //Console.Write($"Var: {rowName, -50}"); var href = itemRow?.GetAttributeValue("href", null); if (href != null) { //Console.Write($" {href}"); var docSub = new HtmlDocument(); docSub.Load($"./DecompFuncs{href}"); var noteSection = docSub.DocumentNode.SelectSingleNode("//section/div[contains(@class,'note note')]"); var xrefRedundant = noteSection?.SelectSingleNode("p/a[contains(@class,'xref')]"); var xrefNodeContent = noteSection?.SelectSingleNode("p/span"); HtmlNode? descrSection = null; if (xrefRedundant != null && xrefNodeContent != null && xrefNodeContent.InnerText.StartsWith("This is a redundant F instruction")) { descrSection = docSub.DocumentNode.SelectSingleNode("//section[2]"); functionIns.RedundantName = xrefRedundant.InnerText; //Console.Write($"{xrefRedundant.InnerText}"); } else { descrSection = docSub.DocumentNode.SelectSingleNode("//section[1]"); } if (descrSection != null) { var descrText = descrSection?.InnerText; if(descrText != null) { descrText = descrText.Replace("\r", "").Replace("\n", "").Trim(); functionIns.Description = descrText; //Console.Write($" {descrText}"); } } } functions.Add(rowName, functionIns); //compatibility matrix //for (int i = 1; i < columns?.Count - 1; i++) { // bool isChecked = columns?.ElementAtOrDefault(i)?.SelectSingleNode("p")?.InnerHtml != ""; // Console.Write($"{(isChecked ? "1" : "0")}, "); //} } } var funcsJson = JsonSerializer.Serialize(functions, new JsonSerializerOptions { WriteIndented = true }); Console.WriteLine(funcsJson); } static async Task GetSystemRegisters () { var addressExceptions = new List(); var progLoc = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; var progFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); var sysVarsPath = Path.Combine(progFilesPath, sysVarsLoc); Directory.SetCurrentDirectory(progLoc); File.Copy(sysVarsPath, "./SysVars.chm", true); var startInfo = new ProcessStartInfo { WorkingDirectory = progLoc, FileName = "hh.exe", Arguments = $"-decompile ./Decomp ./SysVars.chm", }; //call the hh.exe decompiler for chm if (!File.Exists("./Decomp/topics/availability.html")) { var proc = Process.Start(startInfo)!; proc.WaitForExit(); } var doc = new HtmlDocument(); doc.Load("./Decomp/topics/availability.html"); //[contains(@class, 'table mainbody')] foreach (HtmlNode table in doc.DocumentNode.SelectNodes("//table[1]")) { var rows = table?.SelectSingleNode("tbody")?.SelectNodes("tr"); if (rows == null) continue; string lastRegisterName = "Name"; int iSystemRegister = 0; foreach (var row in rows) { var columns = row.SelectNodes("td"); if (columns == null) continue; //get var name var varNameNode = columns?.FirstOrDefault()?.SelectSingleNode("p/a[contains(@class,'xref')]"); string registerAddress; int iterateStart; if (varNameNode != null) { lastRegisterName = varNameNode.InnerText; //get second col var regAddressNode = columns?.ElementAtOrDefault(1)?.SelectSingleNode("p"); registerAddress = regAddressNode?.InnerText ?? "Null"; iterateStart = 2; } else { //get first col var regAddressNode = columns?.ElementAtOrDefault(0)?.SelectSingleNode("p"); registerAddress = regAddressNode?.InnerText ?? "Null"; iterateStart = 1; } //filter the address for annotations var regexAnnotation = new Regex(@"\(.*\)"); var matchAnnotation = regexAnnotation.Match(registerAddress); if (matchAnnotation.Success) { registerAddress = regexAnnotation.Replace(registerAddress, ""); addressExceptions.Add(new AddressException { ForSysRegister = lastRegisterName, ExceptionTitle = matchAnnotation.Value, }); } Console.Write($"Var: {lastRegisterName} | {registerAddress} ".PadRight(100, ' ')); for (int i = iterateStart, j = 0; i < columns?.Count + 1; i++) { if (j >= plcGroups.Count - 1) continue; var group = plcGroups.Keys.ToList()[j]; bool isChecked = columns?.ElementAtOrDefault(i)?.SelectSingleNode("p")?.InnerHtml != ""; Console.Write($"{(isChecked ? "1" : "0")}, "); j++; } Console.WriteLine(); } } foreach (var item in addressExceptions.DistinctBy(x => x.ExceptionTitle)) { Console.WriteLine($"{item.ForSysRegister} - {item.ExceptionTitle}"); } } }