Szf parsing
This commit is contained in:
parent
aa71f08a51
commit
2e212e2c37
@ -1,27 +1,133 @@
|
|||||||
namespace SessionZero.Data;
|
namespace SessionZero.Data;
|
||||||
|
|
||||||
public class Dataset
|
public class Dataset : SzfObject
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
public string DatasetType { get; set; } = string.Empty;
|
public string DatasetType { get; set; } = string.Empty;
|
||||||
public string Description { get; set; } = string.Empty;
|
|
||||||
public string ImageUrl { get; set; } = string.Empty;
|
public string ImageUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// Collection of entries within this dataset
|
||||||
|
public List<DatasetEntry> Entries { get; set; } = [];
|
||||||
|
|
||||||
|
// Additional metadata fields for .szf compatibility
|
||||||
|
public Dictionary<string, object> Metadata { get; set; } = new();
|
||||||
|
|
||||||
|
public override SzfObjectType ObjectType => SzfObjectType.Dataset;
|
||||||
|
|
||||||
|
public override void PopulateFromMetadata(Dictionary<string, SzfField> metadata)
|
||||||
|
{
|
||||||
|
// Call base method to populate common fields
|
||||||
|
base.PopulateFromMetadata(metadata);
|
||||||
|
|
||||||
|
// Populate dataset-specific fields
|
||||||
|
if (metadata.TryGetValue("Type", out var typeField))
|
||||||
|
DatasetType = typeField.Value?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
if (metadata.TryGetValue("ImageUrl", out var imageField))
|
||||||
|
ImageUrl = imageField.Value?.ToString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SzfValidationResult Validate()
|
||||||
|
{
|
||||||
|
var result = base.Validate();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(DatasetType))
|
||||||
|
result.AddError("DatasetType is required");
|
||||||
|
|
||||||
|
// Validate entries
|
||||||
|
var entryNames = new HashSet<string>();
|
||||||
|
foreach (var entry in Entries)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(entry.Name))
|
||||||
|
{
|
||||||
|
result.AddError("All entries must have a name");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entryNames.Add(entry.Name))
|
||||||
|
result.AddError($"Duplicate entry name: {entry.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DatasetEntry
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
public List<DatasetField> Fields { get; set; } = [];
|
public List<DatasetField> Fields { get; set; } = [];
|
||||||
|
public List<DatasetSection> Sections { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DatasetSection
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public List<DatasetField> Fields { get; set; } = [];
|
||||||
|
public List<DatasetSection> Subsections { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DatasetField
|
public class DatasetField
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public DatasetFieldType Type { get; set; }
|
public DatasetFieldType Type { get; set; }
|
||||||
public object? Value { get; set; } = null;
|
public object? Value { get; set; } = null;
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public bool IsRequired { get; set; } = false;
|
||||||
|
public object? DefaultValue { get; set; }
|
||||||
|
|
||||||
|
// For calculated fields
|
||||||
|
public string? Formula { get; set; }
|
||||||
|
|
||||||
|
// For dataset reference fields
|
||||||
|
public string? ReferencedDatasetId { get; set; }
|
||||||
|
public string? ReferencedDatasetType { get; set; }
|
||||||
|
public bool AllowMultiple { get; set; } = false;
|
||||||
|
|
||||||
|
// For group fields
|
||||||
|
public List<DatasetField> GroupFields { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DatasetFieldType
|
public enum DatasetFieldType
|
||||||
{
|
{
|
||||||
Text,
|
Text,
|
||||||
|
TextField,
|
||||||
Number,
|
Number,
|
||||||
Boolean,
|
Boolean,
|
||||||
Group,
|
Group,
|
||||||
|
Calculated,
|
||||||
|
System,
|
||||||
|
DatasetReference,
|
||||||
|
DatasetType,
|
||||||
|
DatasetReferenceMultiple,
|
||||||
|
DatasetTypeMultiple
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supporting classes for dataset management
|
||||||
|
public class DatasetReference
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public Guid DatasetId { get; set; }
|
||||||
|
public string Version { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public override string ToString() => $"{Name}|{DatasetId}|{Version}";
|
||||||
|
|
||||||
|
public static DatasetReference? Parse(string reference)
|
||||||
|
{
|
||||||
|
var parts = reference.Split('|');
|
||||||
|
if (parts.Length != 3 || !Guid.TryParse(parts[1], out var id))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new DatasetReference
|
||||||
|
{
|
||||||
|
Name = parts[0],
|
||||||
|
DatasetId = id,
|
||||||
|
Version = parts[2]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DatasetValidationResult
|
||||||
|
{
|
||||||
|
public bool IsValid { get; set; }
|
||||||
|
public List<string> Errors { get; set; } = [];
|
||||||
|
public List<string> Warnings { get; set; } = [];
|
||||||
}
|
}
|
250
SessionZero/Data/SzfGenerator.cs
Normal file
250
SessionZero/Data/SzfGenerator.cs
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SessionZero.Data;
|
||||||
|
|
||||||
|
public class SzfGenerator
|
||||||
|
{
|
||||||
|
private readonly SzfGeneratorOptions _options;
|
||||||
|
|
||||||
|
public SzfGenerator(SzfGeneratorOptions? options = null)
|
||||||
|
{
|
||||||
|
_options = options ?? new SzfGeneratorOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate SZF content from any SzfObject
|
||||||
|
/// </summary>
|
||||||
|
public string Generate(SzfObject obj)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
// Generate header
|
||||||
|
GenerateHeader(builder, obj);
|
||||||
|
|
||||||
|
// Generate metadata section
|
||||||
|
GenerateMetadataSection(builder, obj);
|
||||||
|
|
||||||
|
// Generate type-specific content
|
||||||
|
GenerateTypeSpecificContent(builder, obj);
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate the SZF header (!type: and !schema:)
|
||||||
|
/// </summary>
|
||||||
|
private void GenerateHeader(StringBuilder builder, SzfObject obj)
|
||||||
|
{
|
||||||
|
var objectTypeString = obj.ObjectType switch
|
||||||
|
{
|
||||||
|
SzfObjectType.Dataset => "dataset",
|
||||||
|
SzfObjectType.CharacterTemplate => "character_template",
|
||||||
|
SzfObjectType.Character => "character",
|
||||||
|
SzfObjectType.Session => "session",
|
||||||
|
_ => throw new SzfGenerateException($"Unsupported object type: {obj.ObjectType}")
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.AppendLine($"!type: {objectTypeString}");
|
||||||
|
builder.AppendLine($"!schema: {obj.SchemaVersion}");
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate the common metadata section
|
||||||
|
/// </summary>
|
||||||
|
private void GenerateMetadataSection(StringBuilder builder, SzfObject obj)
|
||||||
|
{
|
||||||
|
builder.AppendLine("[Metadata]");
|
||||||
|
|
||||||
|
// Standard metadata fields
|
||||||
|
WriteField(builder, "Name", "text", obj.Name);
|
||||||
|
WriteField(builder, "Version", "text", obj.Version);
|
||||||
|
WriteField(builder, "GUID", "text", obj.Id.ToString());
|
||||||
|
WriteField(builder, "Description", "text-field", obj.Description);
|
||||||
|
WriteField(builder, "Author", "text", obj.Author);
|
||||||
|
|
||||||
|
// Type-specific metadata
|
||||||
|
GenerateTypeSpecificMetadata(builder, obj);
|
||||||
|
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate type-specific metadata fields
|
||||||
|
/// </summary>
|
||||||
|
private void GenerateTypeSpecificMetadata(StringBuilder builder, SzfObject obj)
|
||||||
|
{
|
||||||
|
switch (obj)
|
||||||
|
{
|
||||||
|
case Dataset dataset:
|
||||||
|
WriteField(builder, "Type", "text", dataset.DatasetType);
|
||||||
|
if (!string.IsNullOrEmpty(dataset.ImageUrl))
|
||||||
|
WriteField(builder, "ImageUrl", "text", dataset.ImageUrl);
|
||||||
|
break;
|
||||||
|
// Add other types as they're implemented
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate type-specific content sections
|
||||||
|
/// </summary>
|
||||||
|
private void GenerateTypeSpecificContent(StringBuilder builder, SzfObject obj)
|
||||||
|
{
|
||||||
|
switch (obj)
|
||||||
|
{
|
||||||
|
case Dataset dataset:
|
||||||
|
GenerateDatasetContent(builder, dataset);
|
||||||
|
break;
|
||||||
|
// Add other types as they're implemented
|
||||||
|
default:
|
||||||
|
throw new SzfGenerateException($"Content generation not implemented for type: {obj.GetType().Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate dataset-specific content (entries, sections, fields)
|
||||||
|
/// </summary>
|
||||||
|
private void GenerateDatasetContent(StringBuilder builder, Dataset dataset)
|
||||||
|
{
|
||||||
|
foreach (var entry in dataset.Entries)
|
||||||
|
{
|
||||||
|
// Entry header
|
||||||
|
builder.AppendLine($"[Entry: {entry.Name}]");
|
||||||
|
|
||||||
|
// Entry fields
|
||||||
|
foreach (var field in entry.Fields)
|
||||||
|
{
|
||||||
|
WriteDatasetField(builder, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry sections
|
||||||
|
foreach (var section in entry.Sections)
|
||||||
|
{
|
||||||
|
GenerateDatasetSection(builder, section, $"Entry.{entry.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate dataset sections recursively
|
||||||
|
/// </summary>
|
||||||
|
private void GenerateDatasetSection(StringBuilder builder, DatasetSection section, string parentPath)
|
||||||
|
{
|
||||||
|
var sectionPath = $"{parentPath}.{section.Name}";
|
||||||
|
builder.AppendLine($"[{sectionPath}]");
|
||||||
|
|
||||||
|
// Section fields
|
||||||
|
foreach (var field in section.Fields)
|
||||||
|
{
|
||||||
|
WriteDatasetField(builder, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subsections
|
||||||
|
foreach (var subsection in section.Subsections)
|
||||||
|
{
|
||||||
|
GenerateDatasetSection(builder, subsection, sectionPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a dataset field in SZF format
|
||||||
|
/// </summary>
|
||||||
|
private void WriteDatasetField(StringBuilder builder, DatasetField field)
|
||||||
|
{
|
||||||
|
var typeString = field.Type switch
|
||||||
|
{
|
||||||
|
DatasetFieldType.Text => "text",
|
||||||
|
DatasetFieldType.TextField => "text-field",
|
||||||
|
DatasetFieldType.Number => "number",
|
||||||
|
DatasetFieldType.Boolean => "bool",
|
||||||
|
DatasetFieldType.Group => "group",
|
||||||
|
DatasetFieldType.Calculated => "calculated",
|
||||||
|
DatasetFieldType.System => "system",
|
||||||
|
DatasetFieldType.DatasetReference => "dataset-reference",
|
||||||
|
DatasetFieldType.DatasetType => "dataset-type",
|
||||||
|
DatasetFieldType.DatasetReferenceMultiple => "dataset-reference-multiple",
|
||||||
|
DatasetFieldType.DatasetTypeMultiple => "dataset-type-multiple",
|
||||||
|
_ => "text"
|
||||||
|
};
|
||||||
|
|
||||||
|
var value = FormatFieldValue(field.Value, field.Type);
|
||||||
|
WriteField(builder, field.Name, typeString, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a field in SZF format: Name (type) = value
|
||||||
|
/// </summary>
|
||||||
|
private void WriteField(StringBuilder builder, string name, string type, object? value)
|
||||||
|
{
|
||||||
|
var formattedValue = value?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
if (_options.IndentFields)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"{_options.Indent}{name} ({type}) = {formattedValue}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine($"{name} ({type}) = {formattedValue}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Format field values based on their type
|
||||||
|
/// </summary>
|
||||||
|
private string FormatFieldValue(object? value, DatasetFieldType fieldType)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return fieldType switch
|
||||||
|
{
|
||||||
|
DatasetFieldType.Boolean => (bool)value ? "true" : "false",
|
||||||
|
DatasetFieldType.Number => value.ToString() ?? "0",
|
||||||
|
_ => value.ToString() ?? string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration options for SZF generation
|
||||||
|
/// </summary>
|
||||||
|
public class SzfGeneratorOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to indent field lines within sections
|
||||||
|
/// </summary>
|
||||||
|
public bool IndentFields { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indent string to use for fields (if IndentFields is true)
|
||||||
|
/// </summary>
|
||||||
|
public string Indent { get; set; } = " ";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to include empty sections
|
||||||
|
/// </summary>
|
||||||
|
public bool IncludeEmptySections { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to include fields with null/empty values
|
||||||
|
/// </summary>
|
||||||
|
public bool IncludeEmptyFields { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exception thrown during SZF generation
|
||||||
|
/// </summary>
|
||||||
|
public class SzfGenerateException : Exception
|
||||||
|
{
|
||||||
|
public SzfGenerateException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SzfGenerateException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
115
SessionZero/Data/SzfObject.cs
Normal file
115
SessionZero/Data/SzfObject.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
namespace SessionZero.Data;
|
||||||
|
|
||||||
|
public abstract class SzfObject
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Version { get; set; } = "1.0.0";
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public string Author { get; set; } = string.Empty;
|
||||||
|
public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime ModifiedDate { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public string SchemaVersion { get; set; } = "1.0.0";
|
||||||
|
public Dictionary<string, object> ExtendedMetadata { get; set; } = new();
|
||||||
|
|
||||||
|
public abstract SzfObjectType ObjectType { get; }
|
||||||
|
|
||||||
|
public virtual SzfValidationResult Validate()
|
||||||
|
{
|
||||||
|
var result = new SzfValidationResult { IsValid = true };
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(Name))
|
||||||
|
result.AddError("Name is required");
|
||||||
|
|
||||||
|
if (Id == Guid.Empty)
|
||||||
|
result.AddError("Valid GUID is required");
|
||||||
|
|
||||||
|
if (!IsValidSemanticVersion(Version))
|
||||||
|
result.AddError($"Invalid version format: {Version}");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse SZF content and auto-detect the type
|
||||||
|
/// </summary>
|
||||||
|
public static SzfObject ParseFromSzf(string szfContent)
|
||||||
|
{
|
||||||
|
var parser = new SzfParser();
|
||||||
|
return parser.Parse(szfContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse SZF content into a specific type
|
||||||
|
/// </summary>
|
||||||
|
public static T ParseFromSzf<T>(string szfContent) where T : SzfObject
|
||||||
|
{
|
||||||
|
var parser = new SzfParser();
|
||||||
|
return parser.Parse<T>(szfContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string ToSzfString()
|
||||||
|
{
|
||||||
|
var generator = new SzfGenerator();
|
||||||
|
return generator.Generate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void PopulateFromMetadata(Dictionary<string, SzfField> metadata)
|
||||||
|
{
|
||||||
|
if (metadata.TryGetValue("Name", out var nameField))
|
||||||
|
Name = nameField.Value?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
if (metadata.TryGetValue("Version", out var versionField))
|
||||||
|
Version = versionField.Value?.ToString() ?? "1.0.0";
|
||||||
|
|
||||||
|
if (metadata.TryGetValue("GUID", out var guidField) &&
|
||||||
|
Guid.TryParse(guidField.Value?.ToString(), out var guid))
|
||||||
|
Id = guid;
|
||||||
|
|
||||||
|
if (metadata.TryGetValue("Author", out var authorField))
|
||||||
|
Author = authorField.Value?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
if (metadata.TryGetValue("Description", out var descField))
|
||||||
|
Description = descField.Value?.ToString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidSemanticVersion(string version)
|
||||||
|
{
|
||||||
|
return System.Text.RegularExpressions.Regex.IsMatch(
|
||||||
|
version, @"^\d+\.\d+\.\d+$");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SzfObjectType
|
||||||
|
{
|
||||||
|
Dataset,
|
||||||
|
CharacterTemplate,
|
||||||
|
Character,
|
||||||
|
Session
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SzfValidationResult
|
||||||
|
{
|
||||||
|
public bool IsValid { get; set; } = true;
|
||||||
|
public List<string> Errors { get; set; } = [];
|
||||||
|
public List<string> Warnings { get; set; } = [];
|
||||||
|
|
||||||
|
public void AddError(string error)
|
||||||
|
{
|
||||||
|
Errors.Add(error);
|
||||||
|
IsValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddWarning(string warning)
|
||||||
|
{
|
||||||
|
Warnings.Add(warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SzfField
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
public object? Value { get; set; }
|
||||||
|
}
|
412
SessionZero/Data/SzfParser.cs
Normal file
412
SessionZero/Data/SzfParser.cs
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SessionZero.Data;
|
||||||
|
|
||||||
|
public class SzfParser
|
||||||
|
{
|
||||||
|
private static readonly Regex HeaderRegex = new(@"^!(\w+):\s*(.+)$", RegexOptions.Compiled);
|
||||||
|
private static readonly Regex SectionRegex = new(@"^\[([^\]]+)\]$", RegexOptions.Compiled);
|
||||||
|
private static readonly Regex FieldRegex = new(@"^(\w+)\s*\(([^)]+)\)\s*=\s*(.*)$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse a SZF file content and return the appropriate SzfObject type
|
||||||
|
/// </summary>
|
||||||
|
public T Parse<T>(string szfContent) where T : SzfObject
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(szfContent))
|
||||||
|
throw new SzfParseException("SZF content cannot be empty");
|
||||||
|
|
||||||
|
var lines = szfContent.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(line => line.Trim())
|
||||||
|
.Where(line => !string.IsNullOrEmpty(line))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var parseResult = ParseSzfStructure(lines);
|
||||||
|
|
||||||
|
// Validate that the parsed type matches the requested type
|
||||||
|
ValidateObjectType<T>(parseResult.ObjectType);
|
||||||
|
|
||||||
|
var obj = CreateInstance<T>(parseResult.ObjectType);
|
||||||
|
PopulateSzfObject(obj, parseResult);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse SZF content without specifying the exact type - auto-detects type
|
||||||
|
/// </summary>
|
||||||
|
public SzfObject Parse(string szfContent)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(szfContent))
|
||||||
|
throw new SzfParseException("SZF content cannot be empty");
|
||||||
|
|
||||||
|
var lines = szfContent.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(line => line.Trim())
|
||||||
|
.Where(line => !string.IsNullOrEmpty(line))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var parseResult = ParseSzfStructure(lines);
|
||||||
|
|
||||||
|
var obj = CreateInstanceFromType(parseResult.ObjectType);
|
||||||
|
PopulateSzfObject(obj, parseResult);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an instance of the appropriate type based on SzfObjectType
|
||||||
|
/// </summary>
|
||||||
|
private T CreateInstance<T>(SzfObjectType objectType) where T : SzfObject
|
||||||
|
{
|
||||||
|
var instance = CreateInstanceFromType(objectType);
|
||||||
|
if (instance is T typedInstance)
|
||||||
|
return typedInstance;
|
||||||
|
|
||||||
|
throw new SzfParseException($"Cannot cast {instance.GetType().Name} to {typeof(T).Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an instance based on the object type
|
||||||
|
/// </summary>
|
||||||
|
private SzfObject CreateInstanceFromType(SzfObjectType objectType)
|
||||||
|
{
|
||||||
|
return objectType switch
|
||||||
|
{
|
||||||
|
SzfObjectType.Dataset => new Dataset(),
|
||||||
|
// Add other types as they're implemented
|
||||||
|
// SzfObjectType.CharacterTemplate => new CharacterTemplate(),
|
||||||
|
// SzfObjectType.Character => new Character(),
|
||||||
|
// SzfObjectType.Session => new Session(),
|
||||||
|
_ => throw new SzfParseException($"Unsupported object type: {objectType}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse SZF content into a generic structure before type-specific processing
|
||||||
|
/// </summary>
|
||||||
|
private SzfParseResult ParseSzfStructure(List<string> lines)
|
||||||
|
{
|
||||||
|
var result = new SzfParseResult();
|
||||||
|
var currentSection = new List<string>();
|
||||||
|
var currentSectionName = string.Empty;
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
// Skip comments
|
||||||
|
if (line.StartsWith("//") || line.StartsWith("#"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Parse headers (!type:, !schema:)
|
||||||
|
var headerMatch = HeaderRegex.Match(line);
|
||||||
|
if (headerMatch.Success)
|
||||||
|
{
|
||||||
|
var headerName = headerMatch.Groups[1].Value.ToLower();
|
||||||
|
var headerValue = headerMatch.Groups[2].Value.Trim();
|
||||||
|
|
||||||
|
switch (headerName)
|
||||||
|
{
|
||||||
|
case "type":
|
||||||
|
result.ObjectType = ParseObjectType(headerValue);
|
||||||
|
break;
|
||||||
|
case "schema":
|
||||||
|
result.SchemaVersion = headerValue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result.Headers[headerName] = headerValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse section headers [Section Name]
|
||||||
|
var sectionMatch = SectionRegex.Match(line);
|
||||||
|
if (sectionMatch.Success)
|
||||||
|
{
|
||||||
|
// Process previous section if exists
|
||||||
|
if (!string.IsNullOrEmpty(currentSectionName))
|
||||||
|
{
|
||||||
|
ProcessSection(result, currentSectionName, currentSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start new section
|
||||||
|
currentSectionName = sectionMatch.Groups[1].Value.Trim();
|
||||||
|
currentSection.Clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add line to current section
|
||||||
|
if (!string.IsNullOrEmpty(currentSectionName))
|
||||||
|
{
|
||||||
|
currentSection.Add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process final section
|
||||||
|
if (!string.IsNullOrEmpty(currentSectionName))
|
||||||
|
{
|
||||||
|
ProcessSection(result, currentSectionName, currentSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process an individual section and its fields
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessSection(SzfParseResult result, string sectionName, List<string> sectionLines)
|
||||||
|
{
|
||||||
|
var section = new SzfSection { Name = sectionName };
|
||||||
|
|
||||||
|
foreach (var line in sectionLines)
|
||||||
|
{
|
||||||
|
var fieldMatch = FieldRegex.Match(line);
|
||||||
|
if (fieldMatch.Success)
|
||||||
|
{
|
||||||
|
var field = new SzfField
|
||||||
|
{
|
||||||
|
Name = fieldMatch.Groups[1].Value.Trim(),
|
||||||
|
Type = fieldMatch.Groups[2].Value.Trim(),
|
||||||
|
Value = ParseFieldValue(fieldMatch.Groups[3].Value.Trim(), fieldMatch.Groups[2].Value.Trim())
|
||||||
|
};
|
||||||
|
|
||||||
|
section.Fields.Add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Sections.Add(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse field value based on its declared type
|
||||||
|
/// </summary>
|
||||||
|
private object? ParseFieldValue(string valueString, string fieldType)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(valueString))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return fieldType.ToLower() switch
|
||||||
|
{
|
||||||
|
"text" => valueString,
|
||||||
|
"text-field" => valueString,
|
||||||
|
"number" => ParseNumber(valueString),
|
||||||
|
"bool" => ParseBoolean(valueString),
|
||||||
|
"calculated" => valueString, // Store formula as string
|
||||||
|
"system" => valueString,
|
||||||
|
"dataset-reference" => valueString,
|
||||||
|
"dataset-type" => valueString,
|
||||||
|
"dataset-reference-multiple" => valueString,
|
||||||
|
"dataset-type-multiple" => valueString,
|
||||||
|
"group" => ParseBoolean(valueString), // Groups are typically true/false
|
||||||
|
_ => valueString // Default to string
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse number values (int or double)
|
||||||
|
/// </summary>
|
||||||
|
private object ParseNumber(string value)
|
||||||
|
{
|
||||||
|
if (int.TryParse(value, out var intResult))
|
||||||
|
return intResult;
|
||||||
|
|
||||||
|
if (double.TryParse(value, out var doubleResult))
|
||||||
|
return doubleResult;
|
||||||
|
|
||||||
|
throw new SzfParseException($"Invalid number format: {value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse boolean values
|
||||||
|
/// </summary>
|
||||||
|
private bool ParseBoolean(string value)
|
||||||
|
{
|
||||||
|
return value.ToLower() switch
|
||||||
|
{
|
||||||
|
"true" => true,
|
||||||
|
"false" => false,
|
||||||
|
"1" => true,
|
||||||
|
"0" => false,
|
||||||
|
_ => throw new SzfParseException($"Invalid boolean format: {value}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse object type from string
|
||||||
|
/// </summary>
|
||||||
|
private SzfObjectType ParseObjectType(string typeString)
|
||||||
|
{
|
||||||
|
return typeString.ToLower() switch
|
||||||
|
{
|
||||||
|
"dataset" => SzfObjectType.Dataset,
|
||||||
|
"character_template" => SzfObjectType.CharacterTemplate,
|
||||||
|
"character" => SzfObjectType.Character,
|
||||||
|
"session" => SzfObjectType.Session,
|
||||||
|
_ => throw new SzfParseException($"Unknown object type: {typeString}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validate that the parsed type matches the requested generic type
|
||||||
|
/// </summary>
|
||||||
|
private void ValidateObjectType<T>(SzfObjectType parsedType) where T : SzfObject
|
||||||
|
{
|
||||||
|
var expectedType = typeof(T).Name switch
|
||||||
|
{
|
||||||
|
nameof(Dataset) => SzfObjectType.Dataset,
|
||||||
|
// Add other types as they're implemented
|
||||||
|
_ => throw new SzfParseException($"Unsupported SzfObject type: {typeof(T).Name}")
|
||||||
|
};
|
||||||
|
|
||||||
|
if (parsedType != expectedType)
|
||||||
|
{
|
||||||
|
throw new SzfParseException($"Type mismatch: Expected {expectedType}, found {parsedType}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populate the SzfObject with parsed data
|
||||||
|
/// </summary>
|
||||||
|
private void PopulateSzfObject(SzfObject obj, SzfParseResult parseResult)
|
||||||
|
{
|
||||||
|
// Set schema version
|
||||||
|
obj.SchemaVersion = parseResult.SchemaVersion;
|
||||||
|
|
||||||
|
// Find and process Metadata section
|
||||||
|
var metadataSection =
|
||||||
|
parseResult.Sections.FirstOrDefault(s => s.Name.Equals("Metadata", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (metadataSection != null)
|
||||||
|
{
|
||||||
|
var metadata = metadataSection.Fields.ToDictionary(f => f.Name, f => f);
|
||||||
|
obj.PopulateFromMetadata(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process type-specific content
|
||||||
|
ProcessTypeSpecificContent(obj, parseResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process type-specific content based on the object type
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessTypeSpecificContent(SzfObject obj, SzfParseResult parseResult)
|
||||||
|
{
|
||||||
|
switch (obj)
|
||||||
|
{
|
||||||
|
case Dataset dataset:
|
||||||
|
ProcessDatasetContent(dataset, parseResult);
|
||||||
|
break;
|
||||||
|
// Add other types as they're implemented
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process dataset-specific content (entries and their sections)
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessDatasetContent(Dataset dataset, SzfParseResult parseResult)
|
||||||
|
{
|
||||||
|
var entrySections = parseResult.Sections
|
||||||
|
.Where(s => s.Name.StartsWith("Entry:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var entrySection in entrySections)
|
||||||
|
{
|
||||||
|
var entryName = entrySection.Name.Substring(6).Trim(); // Remove "Entry: " prefix
|
||||||
|
var entry = new DatasetEntry { Name = entryName };
|
||||||
|
|
||||||
|
// Add fields directly on the entry
|
||||||
|
foreach (var field in entrySection.Fields)
|
||||||
|
{
|
||||||
|
entry.Fields.Add(ConvertToDatasetField(field));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find subsections for this entry
|
||||||
|
var entrySubsections = parseResult.Sections
|
||||||
|
.Where(s => s.Name.StartsWith($"Entry.{entryName}.", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var subsection in entrySubsections)
|
||||||
|
{
|
||||||
|
var sectionName = subsection.Name.Substring($"Entry.{entryName}.".Length);
|
||||||
|
var datasetSection = new DatasetSection { Name = sectionName };
|
||||||
|
|
||||||
|
foreach (var field in subsection.Fields)
|
||||||
|
{
|
||||||
|
datasetSection.Fields.Add(ConvertToDatasetField(field));
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Sections.Add(datasetSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataset.Entries.Add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert SzfField to DatasetField
|
||||||
|
/// </summary>
|
||||||
|
private DatasetField ConvertToDatasetField(SzfField szfField)
|
||||||
|
{
|
||||||
|
return new DatasetField
|
||||||
|
{
|
||||||
|
Name = szfField.Name,
|
||||||
|
Type = ParseDatasetFieldType(szfField.Type),
|
||||||
|
Value = szfField.Value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse DatasetFieldType from string
|
||||||
|
/// </summary>
|
||||||
|
private DatasetFieldType ParseDatasetFieldType(string typeString)
|
||||||
|
{
|
||||||
|
return typeString.ToLower() switch
|
||||||
|
{
|
||||||
|
"text" => DatasetFieldType.Text,
|
||||||
|
"text-field" => DatasetFieldType.TextField,
|
||||||
|
"number" => DatasetFieldType.Number,
|
||||||
|
"bool" => DatasetFieldType.Boolean,
|
||||||
|
"group" => DatasetFieldType.Group,
|
||||||
|
"calculated" => DatasetFieldType.Calculated,
|
||||||
|
"system" => DatasetFieldType.System,
|
||||||
|
"dataset-reference" => DatasetFieldType.DatasetReference,
|
||||||
|
"dataset-type" => DatasetFieldType.DatasetType,
|
||||||
|
"dataset-reference-multiple" => DatasetFieldType.DatasetReferenceMultiple,
|
||||||
|
"dataset-type-multiple" => DatasetFieldType.DatasetTypeMultiple,
|
||||||
|
_ => DatasetFieldType.Text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal class to hold parsed SZF structure
|
||||||
|
/// </summary>
|
||||||
|
internal class SzfParseResult
|
||||||
|
{
|
||||||
|
public SzfObjectType ObjectType { get; set; }
|
||||||
|
public string SchemaVersion { get; set; } = "1.0.0";
|
||||||
|
public Dictionary<string, string> Headers { get; set; } = new();
|
||||||
|
public List<SzfSection> Sections { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal class representing a parsed section
|
||||||
|
/// </summary>
|
||||||
|
internal class SzfSection
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public List<SzfField> Fields { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exception thrown during SZF parsing
|
||||||
|
/// </summary>
|
||||||
|
public class SzfParseException : Exception
|
||||||
|
{
|
||||||
|
public SzfParseException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SzfParseException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,183 @@
|
|||||||
@page "/Test"
|
@page "/szf-parser-test"
|
||||||
|
@using SessionZero.Data
|
||||||
|
@using Microsoft.Extensions.Logging
|
||||||
|
@inject SzfParser SzfParser
|
||||||
|
@inject ILogger<SzfParser> Logger
|
||||||
|
|
||||||
<h3>@_message</h3>
|
<h1 class="page-title">SZF Parser Test Page</h1>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<textarea id="szf-input" class="form-control" @bind="szfCode" rows="20"></textarea>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary mt-2" @onclick="ParseSzf">Parse SZF</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
@if (parsedObject != null)
|
||||||
|
{
|
||||||
|
<h4>Parsed Object Metadata</h4>
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr><th>Object Type</th><td>@parsedObject.ObjectType</td></tr>
|
||||||
|
<tr><th>Schema Version</th><td>@parsedObject.SchemaVersion</td></tr>
|
||||||
|
<tr><th>Name</th><td>@parsedObject.Name</td></tr>
|
||||||
|
<tr><th>Version</th><td>@parsedObject.Version</td></tr>
|
||||||
|
<tr><th>ID</th><td>@parsedObject.Id</td></tr>
|
||||||
|
<tr><th>Author</th><td>@parsedObject.Author</td></tr>
|
||||||
|
<tr><th>Description</th><td>@parsedObject.Description</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
@if (parsedObject.ExtendedMetadata.Any())
|
||||||
|
{
|
||||||
|
<h5>Extended Metadata</h5>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Key</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var meta in parsedObject.ExtendedMetadata)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@meta.Key</td>
|
||||||
|
<td>@meta.Value</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (parsedObject is Dataset dataset && dataset.Entries.Any())
|
||||||
|
{
|
||||||
|
<h4 class="mt-4">Entries</h4>
|
||||||
|
@foreach (var entry in dataset.Entries)
|
||||||
|
{
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>@entry.Name</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@if (entry.Fields.Any())
|
||||||
|
{
|
||||||
|
<table class="table table-bordered table-sm">
|
||||||
|
<tbody>
|
||||||
|
@foreach (var field in entry.Fields)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td style="width: 30%;"><strong>@field.Name</strong></td>
|
||||||
|
<td>@field.Value</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
@foreach (var section in entry.Sections)
|
||||||
|
{
|
||||||
|
@RenderSection(section)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if (errorMessages.Any())
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger mt-3">
|
||||||
|
<h4>Errors</h4>
|
||||||
|
<ul>
|
||||||
|
@foreach (var error in errorMessages)
|
||||||
|
{
|
||||||
|
<li>@error</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<h4>Logs</h4>
|
||||||
|
<pre class="bg-light p-3">@string.Join("\n", logMessages)</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string szfCode = string.Empty;
|
||||||
|
private SzfObject? parsedObject;
|
||||||
|
private List<string> logMessages = new List<string>();
|
||||||
|
private List<string> errorMessages = new List<string>();
|
||||||
|
|
||||||
|
private void ParseSzf()
|
||||||
|
{
|
||||||
|
logMessages.Clear();
|
||||||
|
errorMessages.Clear();
|
||||||
|
parsedObject = null;
|
||||||
|
|
||||||
|
Log("Starting SZF parsing...");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(szfCode))
|
||||||
|
{
|
||||||
|
const string message = "SZF code input is empty.";
|
||||||
|
LogError(message);
|
||||||
|
errorMessages.Add(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
parsedObject = SzfParser.Parse(szfCode);
|
||||||
|
Log("SZF content parsed successfully.");
|
||||||
|
Log($"Object Type: {parsedObject.ObjectType}");
|
||||||
|
Log($"Schema Version: {parsedObject.SchemaVersion}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
const string message = "An error occurred during parsing.";
|
||||||
|
LogError(message, ex);
|
||||||
|
errorMessages.Add($"{message} See logs for details.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Log(string message)
|
||||||
|
{
|
||||||
|
Logger.LogInformation(message);
|
||||||
|
logMessages.Add($"[INFO] {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogError(string message, Exception? ex = null)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, message);
|
||||||
|
logMessages.Add($"[ERROR] {message}{(ex is not null ? $": {ex.Message}" : "")}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private RenderFragment RenderSection(DatasetSection dataSection) =>@<div class="card mt-2" style="margin-left: 20px;">
|
||||||
|
<div class="card-header py-1">
|
||||||
|
<strong>@dataSection.Name</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body py-2">
|
||||||
|
@if (dataSection.Fields.Any())
|
||||||
|
{
|
||||||
|
<table class="table table-bordered table-sm mb-0">
|
||||||
|
<tbody>
|
||||||
|
@foreach (var field in dataSection.Fields)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td style="width: 30%;"><strong>@field.Name</strong></td>
|
||||||
|
<td>@field.Value</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
@foreach (var subSection in dataSection.Subsections)
|
||||||
|
{
|
||||||
|
@RenderSection(subSection)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
@ -2,6 +2,7 @@ using Blazored.LocalStorage;
|
|||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
using SessionZero;
|
using SessionZero;
|
||||||
|
using SessionZero.Data;
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
builder.RootComponents.Add<App>("#app");
|
builder.RootComponents.Add<App>("#app");
|
||||||
@ -13,6 +14,7 @@ builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.
|
|||||||
builder.Services.AddBlazoredLocalStorage();
|
builder.Services.AddBlazoredLocalStorage();
|
||||||
|
|
||||||
// Register our services
|
// Register our services
|
||||||
|
builder.Services.AddScoped<SzfParser>();
|
||||||
|
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
await builder.Build().RunAsync();
|
@ -289,13 +289,13 @@ Level1Spells (dataset-type-multiple) = spells
|
|||||||
|
|
||||||
## Development Roadmap
|
## Development Roadmap
|
||||||
|
|
||||||
### Phase 1: Core Foundation ✅
|
### Phase 1: Core Foundation
|
||||||
- [] Library system implementation
|
- [] Library system implementation
|
||||||
- [] Template Builder functionality
|
- [] Template Builder functionality
|
||||||
- [] Character Manager interface
|
- [] Character Manager interface
|
||||||
- [] Basic session framework
|
- [] Basic session framework
|
||||||
|
|
||||||
### Phase 2: Enhanced Sessions (Current)
|
### Phase 2: Enhanced Sessions
|
||||||
- [ ] Dataset loading into sessions
|
- [ ] Dataset loading into sessions
|
||||||
- [ ] Advanced session management
|
- [ ] Advanced session management
|
||||||
- [ ] Character-session integration
|
- [ ] Character-session integration
|
||||||
|
Loading…
Reference in New Issue
Block a user