SessionZeroWasm/SessionZero/Data/SzfGenerator.cs
2025-06-30 22:51:23 -05:00

250 lines
7.4 KiB
C#

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)
{
}
}