SzfParser can now parse szf files. Specific parsing rules can be set up in each object.
This commit is contained in:
parent
d86c9db513
commit
69df933609
@ -1,6 +1,37 @@
|
|||||||
// See https://aka.ms/new-console-template for more information
|
using Spectre.Console;
|
||||||
|
using SessionZero.SzfLib.Parser;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
using Spectre.Console;
|
void PrintSection(dynamic section, int indent = 0)
|
||||||
|
{
|
||||||
|
string indentStr = new string(' ', indent * 2);
|
||||||
|
AnsiConsole.MarkupLine($"\n{indentStr}[bold yellow]Section:[/] {section.Name}");
|
||||||
|
if (section.Fields.Count == 0)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine($"{indentStr}[italic]No fields[/]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var table = new Table();
|
||||||
|
table.AddColumn("Field Name");
|
||||||
|
table.AddColumn("Type");
|
||||||
|
table.AddColumn("Value");
|
||||||
|
foreach (var field in section.Fields.Values)
|
||||||
|
{
|
||||||
|
var typedField = (SzfField)field;
|
||||||
|
table.AddRow(typedField.Name, typedField.SzfType.ToString(), typedField.Value ?? "");
|
||||||
|
}
|
||||||
|
AnsiConsole.Write(table);
|
||||||
|
}
|
||||||
|
if (section.Subsections.Count > 0)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine($"{indentStr}[italic]Subsections:[/]");
|
||||||
|
foreach (var sub in section.Subsections.Values)
|
||||||
|
{
|
||||||
|
PrintSection(sub, indent + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AnsiConsole.MarkupLine("[deepskyblue4_1]\n\n" +
|
AnsiConsole.MarkupLine("[deepskyblue4_1]\n\n" +
|
||||||
" █████████ ███████████ ███████████ █████████ █████ █████\n" +
|
" █████████ ███████████ ███████████ █████████ █████ █████\n" +
|
||||||
@ -11,19 +42,32 @@ AnsiConsole.MarkupLine("[deepskyblue4_1]\n\n" +
|
|||||||
" ███ ░███ ████ █ ░███ ░ ░░███ ███ ░███ █ ░███ \n" +
|
" ███ ░███ ████ █ ░███ ░ ░░███ ███ ░███ █ ░███ \n" +
|
||||||
"░░█████████ ███████████ █████ ░░█████████ ███████████ █████\n" +
|
"░░█████████ ███████████ █████ ░░█████████ ███████████ █████\n" +
|
||||||
" ░░░░░░░░░ ░░░░░░░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░░ ░░░░░ \n\n[/]"
|
" ░░░░░░░░░ ░░░░░░░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░░ ░░░░░ \n\n[/]"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
AnsiConsole.MarkupLine("[deepskyblue4_2]A tool for testing the SessionZero SZF Parser[/]");
|
AnsiConsole.MarkupLine("[deepskyblue4_2]A tool for testing the SessionZero SZF Parser[/]");
|
||||||
AnsiConsole.MarkupLine("See [blue][link]https://sessionzero.app/szf-docs.html[/][/] for SZF documentation.");
|
|
||||||
|
|
||||||
struct SZColors
|
var filePath = AnsiConsole.Ask<string>("Enter the path to the [green]SZF file[/]:");
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
{
|
{
|
||||||
public static readonly Color BackgroundColor = Color.Grey19;
|
AnsiConsole.MarkupLine("[red]File not found![/]");
|
||||||
public static readonly Color PrimaryColor = Color.SteelBlue;
|
return;
|
||||||
public static readonly Color PrimaryColorLight = Color.SlateBlue1;
|
}
|
||||||
public static readonly Color SecondaryColor = Color.NavyBlue;
|
|
||||||
public static readonly Color AccentColor = Color.DeepSkyBlue4_2;
|
string szfContent = File.ReadAllText(filePath);
|
||||||
public static readonly Color TextColor = Color.White;
|
var parser = new SzfParser();
|
||||||
public static readonly Color HeadingColor = Color.LightSteelBlue;
|
var szfObject = parser.Parse(szfContent);
|
||||||
|
|
||||||
|
if (szfObject == null)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("[red]Failed to parse SZF file.[/]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLine($"[bold]SZF Type:[/] {szfObject.SzfType}");
|
||||||
|
AnsiConsole.MarkupLine($"[bold]SZF Version:[/] {szfObject.SzfVersion}");
|
||||||
|
AnsiConsole.MarkupLine($"[bold]Number of Sections:[/] {szfObject.Sections.Count}");
|
||||||
|
|
||||||
|
foreach (var section in szfObject.Sections)
|
||||||
|
{
|
||||||
|
PrintSection(section);
|
||||||
}
|
}
|
19
SessionZero.SzfLib/Helpers/SzfHelper.cs
Normal file
19
SessionZero.SzfLib/Helpers/SzfHelper.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using SessionZero.SzfLib.Objects;
|
||||||
|
|
||||||
|
namespace SessionZero.SzfLib.Helpers;
|
||||||
|
|
||||||
|
public static class SzfHelper
|
||||||
|
{
|
||||||
|
public static Type? FindSzfObjectTypeByAttributeType(string szfType)
|
||||||
|
{
|
||||||
|
var obj = Assembly
|
||||||
|
.GetExecutingAssembly()
|
||||||
|
.GetTypes()
|
||||||
|
.FirstOrDefault(t => t.GetCustomAttribute(typeof(SzfObjectAttribute)) is not null && t.GetCustomAttribute<SzfObjectAttribute>()?.TypeIdentifier == szfType);
|
||||||
|
|
||||||
|
if (obj is null) return null;
|
||||||
|
|
||||||
|
return obj.IsAbstract || obj.IsInterface ? null : obj;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,16 @@
|
|||||||
|
using SessionZero.SzfLib.Parser;
|
||||||
|
|
||||||
namespace SessionZero.SzfLib.Objects;
|
namespace SessionZero.SzfLib.Objects;
|
||||||
|
|
||||||
public interface ISzfObject
|
public interface ISzfObject
|
||||||
{
|
{
|
||||||
public string SzfType { get; set; }
|
public string SzfType { get; set; }
|
||||||
|
public string SzfVersion { get; set; }
|
||||||
|
public List<SzfSection> Sections { get; set; }
|
||||||
|
|
||||||
|
public string GetMetadataField(string fieldName);
|
||||||
|
public string GetFieldValue(string sectionName, string fieldName);
|
||||||
|
public string FindFieldValueInSection(SzfSection section, string[] path, int depth, string fieldName);
|
||||||
|
|
||||||
|
public ISzfObject? Parse();
|
||||||
}
|
}
|
@ -1,6 +1,87 @@
|
|||||||
|
using SessionZero.SzfLib.Parser;
|
||||||
|
|
||||||
namespace SessionZero.SzfLib.Objects;
|
namespace SessionZero.SzfLib.Objects;
|
||||||
|
|
||||||
public abstract class SzfObject : ISzfObject
|
public abstract class SzfObject : ISzfObject
|
||||||
{
|
{
|
||||||
public abstract string SzfType { get; set; }
|
public virtual string SzfType { get; set; }
|
||||||
|
public string SzfVersion { get; set; } = "1.0.0";
|
||||||
|
public List<SzfSection> Sections { get; set; } = new();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles specific parsing logic for the SzfObject that overrides this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual ISzfObject? Parse()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of a field in the Metadata section.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public string GetMetadataField(string fieldName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(fieldName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Field name cannot be null or empty.", nameof(fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetFieldValue("Metadata", fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of a field in a specific section. Can handle nested sections using dot notation (e.g., "Section.Subsection").
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sectionName"></param>
|
||||||
|
/// <param name="fieldName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public string GetFieldValue(string sectionName, string fieldName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(sectionName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Section name cannot be null or empty.", nameof(sectionName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(fieldName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Field name cannot be null or empty.", nameof(fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
var sectionPath = sectionName.Split('.');
|
||||||
|
|
||||||
|
foreach (var section in Sections)
|
||||||
|
{
|
||||||
|
var result = FindFieldValueInSection(section, sectionPath, 0, fieldName);
|
||||||
|
if (result != string.Empty) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FindFieldValueInSection(SzfSection section, string[] path, int depth, string fieldName)
|
||||||
|
{
|
||||||
|
if (section.Name != path[depth]) return string.Empty;
|
||||||
|
|
||||||
|
if (depth == path.Length - 1)
|
||||||
|
{
|
||||||
|
if (section.Fields.TryGetValue(fieldName, out var field) && field is SzfField szfField) return szfField.Value ?? string.Empty;
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var subsection in section.Subsections.Values)
|
||||||
|
{
|
||||||
|
var result = FindFieldValueInSection(subsection, path, depth + 1, fieldName);
|
||||||
|
if (result != string.Empty) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -5,6 +5,6 @@ namespace SessionZero.SzfLib.Parser;
|
|||||||
|
|
||||||
public interface ISzfParser
|
public interface ISzfParser
|
||||||
{
|
{
|
||||||
public ISzfFile Parse(string szfContent);
|
public ISzfObject? Parse(string szfContent);
|
||||||
public ISzfObject Parse(ISzfFile file);
|
public ISzfObject? Parse(ISzfFile file);
|
||||||
}
|
}
|
@ -1,17 +1,173 @@
|
|||||||
using SessionZero.SzfLib.File;
|
using SessionZero.SzfLib.File;
|
||||||
|
using SessionZero.SzfLib.Helpers;
|
||||||
using SessionZero.SzfLib.Objects;
|
using SessionZero.SzfLib.Objects;
|
||||||
|
|
||||||
namespace SessionZero.SzfLib.Parser;
|
namespace SessionZero.SzfLib.Parser;
|
||||||
|
|
||||||
public class SzfParser : ISzfParser
|
public class SzfParser : ISzfParser
|
||||||
{
|
{
|
||||||
public ISzfFile Parse(string szfContent)
|
/// <summary>
|
||||||
|
/// Parses the given SZF content and returns the corresponding ISzfObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="szfContent"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ISzfObject? Parse(string szfContent)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var lines = szfContent.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
string szfObjectTypeName = string.Empty;
|
||||||
|
Type? szfObjectType = null;
|
||||||
|
string szfVersion = "1.0.0";
|
||||||
|
var sections = new Dictionary<string, SzfSection>();
|
||||||
|
|
||||||
|
for (int idx = 0; idx < lines.Length; idx++)
|
||||||
|
{
|
||||||
|
var line = lines[idx].Trim();
|
||||||
|
|
||||||
|
if (line.StartsWith("#") || string.IsNullOrWhiteSpace(line)) continue;
|
||||||
|
|
||||||
|
if (line.StartsWith("!type:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var typeIdentifier = line.Substring("!type:".Length).Trim();
|
||||||
|
szfObjectTypeName = typeIdentifier;
|
||||||
|
szfObjectType = SzfHelper.FindSzfObjectTypeByAttributeType(typeIdentifier);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.StartsWith("!schema:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var version = line.Substring("!schema:".Length).Trim();
|
||||||
|
if (!string.IsNullOrEmpty(version)) szfVersion = version;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.StartsWith("[") && line.EndsWith("]"))
|
||||||
|
{
|
||||||
|
var sectionName = line.Substring(1, line.Length - 2).Trim();
|
||||||
|
var sectionFields = new List<string>();
|
||||||
|
|
||||||
|
int i = idx + 1;
|
||||||
|
while (i < lines.Length && !(lines[i].Trim().StartsWith("[") && lines[i].Trim().EndsWith("]")))
|
||||||
|
{
|
||||||
|
var fieldLine = lines[i].Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(fieldLine) && !fieldLine.StartsWith("#")) sectionFields.Add(fieldLine);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = i - 1;
|
||||||
|
|
||||||
|
CreateSection(sectionName, sectionFields, sections);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (szfObjectType != null && sections.Count > 0)
|
||||||
|
{
|
||||||
|
var szfObject = (ISzfObject)Activator.CreateInstance(szfObjectType)!;
|
||||||
|
|
||||||
|
szfObject.SzfType = szfObjectTypeName;
|
||||||
|
szfObject.SzfVersion = szfVersion;
|
||||||
|
|
||||||
|
// Only add root sections (those without a dot in their name)
|
||||||
|
foreach (var section in sections.Values.Where(s => !sections.Keys.Any(k => k.EndsWith("." + s.Name))))
|
||||||
|
{
|
||||||
|
szfObject.Sections.Add(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
return szfObject.Parse() ?? szfObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISzfObject Parse(ISzfFile file)
|
public ISzfObject? Parse(ISzfFile file)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (file == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(file), "File cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(file.Content))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("File content cannot be null or empty.", nameof(file.Content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Parse(file.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SzfSection CreateSection(string sectionPath, IEnumerable<string> fields, Dictionary<string, SzfSection> sections)
|
||||||
|
{
|
||||||
|
var nameParts = sectionPath.Split('.');
|
||||||
|
SzfSection? parent = null;
|
||||||
|
|
||||||
|
string currentPath = "";
|
||||||
|
for (int p = 0; p < nameParts.Length; p++)
|
||||||
|
{
|
||||||
|
currentPath = currentPath == "" ? nameParts[p] : currentPath + "." + nameParts[p];
|
||||||
|
if (!sections.TryGetValue(currentPath, out var section))
|
||||||
|
{
|
||||||
|
section = new SzfSection(nameParts[p]);
|
||||||
|
sections[currentPath] = section;
|
||||||
|
if (parent != null) parent.Subsections[nameParts[p]] = section;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = section;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
var szfField = CreateField(field);
|
||||||
|
parent!.Fields.Add(szfField.Name, szfField);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SzfField CreateField(string field)
|
||||||
|
{
|
||||||
|
// Expected format: FieldName (type) = Value
|
||||||
|
int typeStart = field.IndexOf('(');
|
||||||
|
int typeEnd = field.IndexOf(')');
|
||||||
|
if (typeStart < 0 || typeEnd < 0 || typeEnd < typeStart) throw new FormatException($"Invalid field format: {field}");
|
||||||
|
|
||||||
|
string fieldName = field.Substring(0, typeStart).Trim();
|
||||||
|
string fieldType = field.Substring(typeStart + 1, typeEnd - typeStart - 1).Trim().Replace("-", string.Empty);
|
||||||
|
|
||||||
|
// Find the first '=' after the closing parenthesis
|
||||||
|
int eqIndex = field.IndexOf('=', typeEnd + 1);
|
||||||
|
string fieldValue = eqIndex >= 0 ? field.Substring(eqIndex + 1).Trim() : string.Empty;
|
||||||
|
|
||||||
|
if (!Enum.TryParse(fieldType, true, out SzfFieldType type)) throw new FormatException($"Invalid field type: {fieldType}");
|
||||||
|
|
||||||
|
SzfField szfField = new(fieldName, type) { Value = fieldValue };
|
||||||
|
return szfField;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SzfSection(string name)
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = name;
|
||||||
|
public Dictionary<string, SzfField> Fields { get; set; } = new();
|
||||||
|
public Dictionary<string, SzfSection> Subsections { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class SzfField(string name, SzfFieldType type)
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = name;
|
||||||
|
public SzfFieldType SzfType { get; set; } = type;
|
||||||
|
public string? Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SzfFieldType
|
||||||
|
{
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
Number,
|
||||||
|
Bool,
|
||||||
|
Calculated,
|
||||||
|
System,
|
||||||
|
EntryReference,
|
||||||
|
EntryReferenceList,
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user