Seperation of szf generator. Updated test page
This commit is contained in:
parent
22cce4257d
commit
678c10a872
132
5e_template.szf
Normal file
132
5e_template.szf
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
!type: character_template
|
||||||
|
!schema: 1.0.0
|
||||||
|
|
||||||
|
[Metadata]
|
||||||
|
Name (text) = D&D 5e Character Sheet
|
||||||
|
Version (text) = 1.0.0
|
||||||
|
GUID (text) = 5e0c7a12-d4b9-4f3e-9c87-a1b2c3d4e5f6
|
||||||
|
Description (text-field) = A comprehensive character sheet template for Dungeons & Dragons 5th Edition, covering core stats, skills, combat, and general information.
|
||||||
|
Author (text) = AI Assistant
|
||||||
|
GameSystem (text) = Dungeons & Dragons 5th Edition
|
||||||
|
|
||||||
|
[Required Datasets]
|
||||||
|
DnD5eRaces (dataset-reference) = D&D 5e Races|e1a2b3c4-d5e6-7f8a-9b0c-1d2e3f4a5b6c|1.0.0
|
||||||
|
DnD5eClasses (dataset-reference) = D&D 5e Classes|f1e2d3c4-b5a6-7980-1234-56789abcdef0|1.0.0
|
||||||
|
DnD5eSpells (dataset-reference) = D&D 5e Spells|a1b2c3d4-e5f6-7890-1234-567890abcdef|1.0.0
|
||||||
|
DnD5eEquipment (dataset-reference) = D&D 5e Equipment|b1c2d3e4-f5a6-7890-1234-567890fedcba|1.0.0
|
||||||
|
|
||||||
|
[Section: Character Information]
|
||||||
|
CharacterName (text) =
|
||||||
|
PlayerName (text) =
|
||||||
|
Race (dataset-reference) = DnD5eRaces
|
||||||
|
Class (dataset-reference) = DnD5eClasses
|
||||||
|
Level (number) = 1
|
||||||
|
Background (text) =
|
||||||
|
Alignment (text) =
|
||||||
|
ExperiencePoints (number) = 0
|
||||||
|
ProficiencyBonus (calculated) = 2 + floor((Level - 1) / 4)
|
||||||
|
|
||||||
|
[Section: Ability Scores]
|
||||||
|
Strength (number) = 10
|
||||||
|
Dexterity (number) = 10
|
||||||
|
Constitution (number) = 10
|
||||||
|
Intelligence (number) = 10
|
||||||
|
Wisdom (number) = 10
|
||||||
|
Charisma (number) = 10
|
||||||
|
|
||||||
|
[Section.Ability Scores.Modifiers]
|
||||||
|
StrengthMod (calculated) = floor((Strength - 10) / 2)
|
||||||
|
DexterityMod (calculated) = floor((Dexterity - 10) / 2)
|
||||||
|
ConstitutionMod (calculated) = floor((Constitution - 10) / 2)
|
||||||
|
IntelligenceMod (calculated) = floor((Intelligence - 10) / 2)
|
||||||
|
WisdomMod (calculated) = floor((Wisdom - 10) / 2)
|
||||||
|
CharismaMod (calculated) = floor((Charisma - 10) / 2)
|
||||||
|
|
||||||
|
[Section: Saving Throws]
|
||||||
|
StrengthSave (number) = 0
|
||||||
|
DexteritySave (number) = 0
|
||||||
|
ConstitutionSave (number) = 0
|
||||||
|
IntelligenceSave (number) = 0
|
||||||
|
WisdomSave (number) = 0
|
||||||
|
CharismaSave (number) = 0
|
||||||
|
|
||||||
|
[Section: Skills]
|
||||||
|
Acrobatics (number) = 0
|
||||||
|
AnimalHandling (number) = 0
|
||||||
|
Arcana (number) = 0
|
||||||
|
Athletics (number) = 0
|
||||||
|
Deception (number) = 0
|
||||||
|
History (number) = 0
|
||||||
|
Insight (number) = 0
|
||||||
|
Intimidation (number) = 0
|
||||||
|
Investigation (number) = 0
|
||||||
|
Medicine (number) = 0
|
||||||
|
Nature (number) = 0
|
||||||
|
Perception (number) = 0
|
||||||
|
Performance (number) = 0
|
||||||
|
Persuasion (number) = 0
|
||||||
|
Religion (number) = 0
|
||||||
|
SleightOfHand (number) = 0
|
||||||
|
Stealth (number) = 0
|
||||||
|
Survival (number) = 0
|
||||||
|
|
||||||
|
[Section: Combat]
|
||||||
|
ArmorClass (number) = 0
|
||||||
|
Initiative (calculated) = DexterityMod
|
||||||
|
Speed (number) = 30
|
||||||
|
HitPointsMax (number) = 0
|
||||||
|
HitPointsCurrent (number) = 0
|
||||||
|
TemporaryHitPoints (number) = 0
|
||||||
|
HitDice (text) =
|
||||||
|
DeathSavesSuccess (number) = 0
|
||||||
|
DeathSavesFailure (number) = 0
|
||||||
|
|
||||||
|
[Section: Senses]
|
||||||
|
PassivePerception (calculated) = 10 + Perception
|
||||||
|
Darkvision (text) =
|
||||||
|
|
||||||
|
[Section: Feats & Traits]
|
||||||
|
Feats (text-field) =
|
||||||
|
RacialTraits (text-field) =
|
||||||
|
ClassFeatures (text-field) =
|
||||||
|
OtherProficienciesAndLanguages (text-field) =
|
||||||
|
|
||||||
|
[Section: Equipment]
|
||||||
|
CopperPieces (number) = 0
|
||||||
|
SilverPieces (number) = 0
|
||||||
|
ElectrumPieces (number) = 0
|
||||||
|
GoldPieces (number) = 0
|
||||||
|
PlatinumPieces (number) = 0
|
||||||
|
Inventory (text-field) =
|
||||||
|
|
||||||
|
[Section: Spellcasting]
|
||||||
|
SpellcastingAbility (text) =
|
||||||
|
SpellSaveDC (number) = 0
|
||||||
|
SpellAttackBonus (number) = 0
|
||||||
|
|
||||||
|
[Section.Spellcasting.Cantrips]
|
||||||
|
Cantrips (dataset-type-multiple) = DnD5eSpells
|
||||||
|
|
||||||
|
[Section.Spellcasting.Level1Spells]
|
||||||
|
Level1SlotsTotal (number) = 0
|
||||||
|
Level1SlotsUsed (number) = 0
|
||||||
|
Level1Spells (dataset-type-multiple) = DnD5eSpells
|
||||||
|
|
||||||
|
[Section.Spellcasting.Level2Spells]
|
||||||
|
Level2SlotsTotal (number) = 0
|
||||||
|
Level2SlotsUsed (number) = 0
|
||||||
|
Level2Spells (dataset-type-multiple) = DnD5eSpells
|
||||||
|
|
||||||
|
[Section.Spellcasting.Level3Spells]
|
||||||
|
Level3SlotsTotal (number) = 0
|
||||||
|
Level3SlotsUsed (number) = 0
|
||||||
|
Level3Spells (dataset-type-multiple) = DnD5eSpells
|
||||||
|
|
||||||
|
[Section: Character Details]
|
||||||
|
PersonalityTraits (text-field) =
|
||||||
|
Ideals (text-field) =
|
||||||
|
Bonds (text-field) =
|
||||||
|
Flaws (text-field) =
|
||||||
|
Backstory (text-field) =
|
||||||
|
AlliesAndOrganizations (text-field) =
|
||||||
|
AdditionalNotes (text-field) =
|
@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@ -9,7 +8,7 @@ public class CharacterTemplate : SzfObject
|
|||||||
{
|
{
|
||||||
public string GameSystem { get; set; } = string.Empty;
|
public string GameSystem { get; set; } = string.Empty;
|
||||||
public List<RequiredDatasetReference> RequiredDatasets { get; set; } = new();
|
public List<RequiredDatasetReference> RequiredDatasets { get; set; } = new();
|
||||||
public List<TemplateSection> Sections { get; set; } = new(); // Top-level sections
|
public List<TemplateSection> Sections { get; set; } = new();
|
||||||
|
|
||||||
public override SzfObjectType ObjectType => SzfObjectType.CharacterTemplate;
|
public override SzfObjectType ObjectType => SzfObjectType.CharacterTemplate;
|
||||||
|
|
||||||
@ -23,105 +22,81 @@ public class CharacterTemplate : SzfObject
|
|||||||
|
|
||||||
public override void ParseSections(List<SzfSection> sections)
|
public override void ParseSections(List<SzfSection> sections)
|
||||||
{
|
{
|
||||||
// Parse "Required Datasets" section
|
// Parse [Required Datasets] section
|
||||||
var requiredDatasetsSection =
|
var requiredDatasetsSection =
|
||||||
sections.FirstOrDefault(s => s.Name.Equals("Required Datasets", StringComparison.OrdinalIgnoreCase));
|
sections.FirstOrDefault(s => s.Name.Equals("Required Datasets", StringComparison.OrdinalIgnoreCase));
|
||||||
if (requiredDatasetsSection != null)
|
if (requiredDatasetsSection != null)
|
||||||
{
|
{
|
||||||
foreach (var field in requiredDatasetsSection.Fields)
|
foreach (var field in requiredDatasetsSection.Fields)
|
||||||
{
|
{
|
||||||
var datasetReference = DatasetReference.Parse(field.Value?.ToString() ?? string.Empty);
|
var reference = DatasetReference.Parse(field.Value?.ToString() ?? string.Empty);
|
||||||
if (datasetReference != null)
|
if (reference != null)
|
||||||
{
|
{
|
||||||
RequiredDatasets.Add(new RequiredDatasetReference
|
RequiredDatasets.Add(new RequiredDatasetReference
|
||||||
{
|
{
|
||||||
Alias = field.Name,
|
Alias = field.Name,
|
||||||
Reference = datasetReference
|
Reference = reference
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse and build the hierarchical template sections
|
// Parse [Section: ...] and [Section....] sections
|
||||||
// Use a map to quickly find existing sections by their full path for nesting
|
|
||||||
var sectionPathMap = new Dictionary<string, TemplateSection>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
foreach (var szfSection in sections)
|
foreach (var szfSection in sections)
|
||||||
{
|
{
|
||||||
// Only process sections that start with "Section:"
|
if (szfSection.Name.StartsWith("Section:", StringComparison.OrdinalIgnoreCase))
|
||||||
if (!szfSection.Name.StartsWith("Section:", StringComparison.OrdinalIgnoreCase)) continue;
|
|
||||||
|
|
||||||
// Normalize the section name (e.g., "Section:Ability Scores.Modifiers" to "Ability Scores.Modifiers")
|
|
||||||
var rawSectionPath = szfSection.Name.Substring("Section:".Length).Trim();
|
|
||||||
var nameParts = rawSectionPath.Split('.');
|
|
||||||
|
|
||||||
TemplateSection? currentParentSection = null;
|
|
||||||
List<TemplateSection> currentSectionList = Sections; // Start from the top-level Sections list
|
|
||||||
|
|
||||||
string accumulatedPath = "Section"; // Used for dictionary key to ensure uniqueness and hierarchy
|
|
||||||
|
|
||||||
for (int i = 0; i < nameParts.Length; i++)
|
|
||||||
{
|
{
|
||||||
var sectionNamePart = nameParts[i];
|
var templateSection = new TemplateSection
|
||||||
accumulatedPath += "." + sectionNamePart; // Build the full path for the map key
|
|
||||||
|
|
||||||
TemplateSection? targetSection = null;
|
|
||||||
|
|
||||||
// Check if this specific section (at this level of hierarchy) already exists
|
|
||||||
if (sectionPathMap.TryGetValue(accumulatedPath, out var existingSection))
|
|
||||||
{
|
{
|
||||||
targetSection = existingSection;
|
Name = szfSection.Name.Replace("Section:", "").Trim()
|
||||||
}
|
};
|
||||||
else
|
|
||||||
{
|
|
||||||
// Create a new section if it doesn't exist
|
|
||||||
var newSection = new TemplateSection { Name = sectionNamePart };
|
|
||||||
sectionPathMap[accumulatedPath] = newSection;
|
|
||||||
|
|
||||||
if (currentParentSection == null)
|
|
||||||
{
|
|
||||||
// This is a top-level section
|
|
||||||
currentSectionList.Add(newSection);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This is a subsection of the current parent
|
|
||||||
currentParentSection.Subsections.Add(newSection);
|
|
||||||
}
|
|
||||||
|
|
||||||
targetSection = newSection;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentParentSection = targetSection; // Move down the hierarchy
|
|
||||||
currentSectionList =
|
|
||||||
targetSection.Subsections; // Next iteration will look in subsections of this section
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once we've traversed/created the full path, currentParentSection points to the innermost section.
|
|
||||||
// Now populate its fields from the SzfSection.
|
|
||||||
if (currentParentSection != null)
|
|
||||||
{
|
|
||||||
foreach (var field in szfSection.Fields)
|
foreach (var field in szfSection.Fields)
|
||||||
{
|
{
|
||||||
currentParentSection.Fields.Add(ConvertToTemplateField(field));
|
templateSection.Fields.Add(ConvertToTemplateField(field));
|
||||||
|
}
|
||||||
|
|
||||||
|
Sections.Add(templateSection);
|
||||||
|
}
|
||||||
|
else if (szfSection.Name.StartsWith("Section.", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// Handle subsections by finding their parent
|
||||||
|
var parts = szfSection.Name.Split('.');
|
||||||
|
if (parts.Length >= 2)
|
||||||
|
{
|
||||||
|
var parentSectionName = parts[0] + ":" + parts[1].Trim(); // Reconstruct parent name
|
||||||
|
var parentSection = Sections.FirstOrDefault(s =>
|
||||||
|
s.Name.Equals(parentSectionName.Replace("Section:", "").Trim(),
|
||||||
|
StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (parentSection != null)
|
||||||
|
{
|
||||||
|
var subSection = new TemplateSection
|
||||||
|
{
|
||||||
|
Name = string.Join(".", parts.Skip(2)).Trim() // Name for the subsection
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var field in szfSection.Fields)
|
||||||
|
{
|
||||||
|
subSection.Fields.Add(ConvertToTemplateField(field));
|
||||||
|
}
|
||||||
|
|
||||||
|
parentSection.Subsections.Add(subSection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private TemplateField ConvertToTemplateField(SzfField szfField)
|
private TemplateField ConvertToTemplateField(SzfField szfField)
|
||||||
{
|
{
|
||||||
return new TemplateField
|
return new TemplateField
|
||||||
{
|
{
|
||||||
Name = szfField.Name,
|
Name = szfField.Name,
|
||||||
Type = ParseDatasetFieldType(szfField.Type), // Reusing DatasetFieldType for consistency
|
Type = ParseDatasetFieldType(szfField.Type),
|
||||||
Value = szfField.Value,
|
Value = szfField.Value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to parse DatasetFieldType, can be shared or defined here.
|
|
||||||
// Copying from Dataset.cs for now, consider a common utility.
|
|
||||||
private DatasetFieldType ParseDatasetFieldType(string typeString)
|
private DatasetFieldType ParseDatasetFieldType(string typeString)
|
||||||
{
|
{
|
||||||
return typeString.ToLower() switch
|
return typeString.ToLower() switch
|
||||||
@ -130,26 +105,86 @@ public class CharacterTemplate : SzfObject
|
|||||||
"text-field" => DatasetFieldType.TextField,
|
"text-field" => DatasetFieldType.TextField,
|
||||||
"number" => DatasetFieldType.Number,
|
"number" => DatasetFieldType.Number,
|
||||||
"bool" => DatasetFieldType.Boolean,
|
"bool" => DatasetFieldType.Boolean,
|
||||||
"group" => DatasetFieldType.Group,
|
|
||||||
"calculated" => DatasetFieldType.Calculated,
|
"calculated" => DatasetFieldType.Calculated,
|
||||||
"system" => DatasetFieldType.System,
|
"system" => DatasetFieldType.System,
|
||||||
"dataset-reference" => DatasetFieldType.DatasetReference,
|
"dataset-reference" => DatasetFieldType.DatasetReference,
|
||||||
"dataset-type" => DatasetFieldType.DatasetType,
|
"dataset-type" => DatasetFieldType.DatasetType,
|
||||||
"dataset-reference-multiple" => DatasetFieldType.DatasetReferenceMultiple,
|
"dataset-reference-multiple" => DatasetFieldType.DatasetReferenceMultiple,
|
||||||
"dataset-type-multiple" => DatasetFieldType.DatasetTypeMultiple,
|
"dataset-type-multiple" => DatasetFieldType.DatasetTypeMultiple,
|
||||||
_ => DatasetFieldType.Text
|
"group" => DatasetFieldType.Group,
|
||||||
|
_ => DatasetFieldType.Text // Default to text if unknown
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void GenerateMetadata(SzfFieldWriter writer)
|
||||||
|
{
|
||||||
|
// Call base to ensure common metadata is generated first if base had any specific generation logic
|
||||||
|
base.GenerateMetadata(writer);
|
||||||
|
|
||||||
|
writer.WriteField("GameSystem", "text", GameSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GenerateContent(SzfFieldWriter writer)
|
||||||
|
{
|
||||||
|
// Generate [Required Datasets] section
|
||||||
|
if (RequiredDatasets.Any() || writer.Options.IncludeEmptySections)
|
||||||
|
{
|
||||||
|
writer.AppendSectionHeader("Required Datasets");
|
||||||
|
foreach (var reqDataset in RequiredDatasets)
|
||||||
|
{
|
||||||
|
writer.WriteField(reqDataset.Alias, "dataset-reference", reqDataset.Reference?.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Template Sections
|
||||||
|
foreach (var section in Sections)
|
||||||
|
{
|
||||||
|
GenerateTemplateSection(writer, section, "Section");
|
||||||
|
writer.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateTemplateSection(SzfFieldWriter writer, TemplateSection section, string parentPath)
|
||||||
|
{
|
||||||
|
var currentPath = $"{parentPath}: {section.Name}";
|
||||||
|
writer.AppendSectionHeader(currentPath);
|
||||||
|
|
||||||
|
foreach (var field in section.Fields)
|
||||||
|
{
|
||||||
|
writer.WriteField(field.Name, field.Type, field.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var subSection in section.Subsections)
|
||||||
|
{
|
||||||
|
GenerateTemplateSubSection(writer, subSection, $"Section.{section.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateTemplateSubSection(SzfFieldWriter writer, TemplateSection subSection, string parentPath)
|
||||||
|
{
|
||||||
|
var currentPath = $"{parentPath}.{subSection.Name}";
|
||||||
|
writer.AppendSectionHeader(currentPath);
|
||||||
|
|
||||||
|
foreach (var field in subSection.Fields)
|
||||||
|
{
|
||||||
|
writer.WriteField(field.Name, field.Type, field.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var nestedSubSection in subSection.Subsections)
|
||||||
|
{
|
||||||
|
// Handle deeper nesting if needed, recursively calling this method
|
||||||
|
GenerateTemplateSubSection(writer, nestedSubSection, currentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override SzfValidationResult Validate()
|
public override SzfValidationResult Validate()
|
||||||
{
|
{
|
||||||
var result = base.Validate();
|
var result = base.Validate(); // Run base validation first
|
||||||
|
|
||||||
// if (string.IsNullOrWhiteSpace(GameSystem))
|
if (string.IsNullOrWhiteSpace(GameSystem))
|
||||||
// result.AddError("GameSystem is required for CharacterTemplate");
|
result.AddError("GameSystem is required for CharacterTemplate");
|
||||||
|
|
||||||
// Further validation for required datasets and template sections can be added here
|
|
||||||
// e.g., checking for duplicate section names or missing required fields.
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -165,7 +200,7 @@ public class TemplateSection
|
|||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public List<TemplateField> Fields { get; set; } = new();
|
public List<TemplateField> Fields { get; set; } = new();
|
||||||
public List<TemplateSection> Subsections { get; set; } = new();
|
public List<TemplateSection> Subsections { get; set; } = new(); // For nested sections
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TemplateField
|
public class TemplateField
|
||||||
|
@ -1,118 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace SessionZero.Data;
|
namespace SessionZero.Data;
|
||||||
|
|
||||||
[SzfObject(SzfObjectType.Dataset, "dataset")]
|
[SzfObject(SzfObjectType.Dataset, "dataset")]
|
||||||
public class Dataset : SzfObject
|
public class Dataset : SzfObject
|
||||||
{
|
{
|
||||||
public string DatasetType { get; set; } = string.Empty;
|
public string DatasetType { get; set; } = string.Empty;
|
||||||
public string ImageUrl { get; set; } = string.Empty;
|
public string ImageUrl { get; set; } = string.Empty; // New field for dataset image
|
||||||
|
public List<DatasetEntry> Entries { get; set; } = new();
|
||||||
// Collection of entries within this dataset
|
public Dictionary<string, object> Metadata { get; set; } = new(); // To store other metadata fields
|
||||||
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 SzfObjectType ObjectType => SzfObjectType.Dataset;
|
||||||
|
|
||||||
public override void PopulateFromMetadata(Dictionary<string, SzfField> metadata)
|
public override void PopulateFromMetadata(Dictionary<string, SzfField> metadata)
|
||||||
{
|
{
|
||||||
// Call base method to populate common fields
|
|
||||||
base.PopulateFromMetadata(metadata);
|
base.PopulateFromMetadata(metadata);
|
||||||
|
|
||||||
// Populate dataset-specific fields
|
|
||||||
if (metadata.TryGetValue("Type", out var typeField))
|
if (metadata.TryGetValue("Type", out var typeField))
|
||||||
DatasetType = typeField.Value?.ToString() ?? string.Empty;
|
DatasetType = typeField.Value?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
if (metadata.TryGetValue("ImageUrl", out var imageField))
|
if (metadata.TryGetValue("ImageUrl", out var imageUrlField))
|
||||||
ImageUrl = imageField.Value?.ToString() ?? string.Empty;
|
ImageUrl = imageUrlField.Value?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
// Store any other metadata fields not explicitly mapped
|
||||||
|
foreach (var kvp in metadata)
|
||||||
|
{
|
||||||
|
if (!new[] { "Name", "Version", "GUID", "Description", "Author", "Type", "ImageUrl" }
|
||||||
|
.Contains(kvp.Key, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Metadata[kvp.Key] = kvp.Value.Value!;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parses sections specific to the Dataset object, such as entries and their subsections.
|
|
||||||
/// This method is called by the SzfParser to populate Dataset-specific data.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sections">A list of all parsed sections from the SZF file.</param>
|
|
||||||
public override void ParseSections(List<SzfSection> sections)
|
public override void ParseSections(List<SzfSection> sections)
|
||||||
{
|
{
|
||||||
// Dictionary to hold DatasetEntry objects by their name for easy lookup
|
var currentEntry = (DatasetEntry?)null;
|
||||||
var entriesByName = new Dictionary<string, DatasetEntry>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
// First pass: Identify and populate top-level DatasetEntry objects
|
|
||||||
foreach (var szfSection in sections)
|
foreach (var szfSection in sections)
|
||||||
{
|
{
|
||||||
if (szfSection.Name.StartsWith("Entry:", StringComparison.OrdinalIgnoreCase))
|
if (szfSection.Name.StartsWith("Entry:", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var entryName = szfSection.Name.Substring("Entry:".Length).Trim();
|
currentEntry = new DatasetEntry
|
||||||
if (string.IsNullOrEmpty(entryName)) continue;
|
{
|
||||||
|
Name = szfSection.Name.Replace("Entry:", "").Trim()
|
||||||
|
};
|
||||||
|
|
||||||
var newEntry = new DatasetEntry { Name = entryName };
|
|
||||||
foreach (var field in szfSection.Fields)
|
foreach (var field in szfSection.Fields)
|
||||||
{
|
{
|
||||||
newEntry.Fields.Add(ConvertToDatasetField(field));
|
currentEntry.Fields.Add(ConvertToDatasetField(field));
|
||||||
}
|
}
|
||||||
|
|
||||||
Entries.Add(newEntry);
|
Entries.Add(currentEntry);
|
||||||
entriesByName[entryName] = newEntry;
|
|
||||||
}
|
}
|
||||||
}
|
else if (szfSection.Name.StartsWith("Entry.", StringComparison.OrdinalIgnoreCase) && currentEntry != null)
|
||||||
|
|
||||||
// Second pass: Populate sections and subsections for each entry
|
|
||||||
foreach (var szfSection in sections)
|
|
||||||
{
|
|
||||||
// We are looking for sections like "Entry.EntryName.SectionName" or "Entry.EntryName.SectionName.SubSectionName"
|
|
||||||
if (!szfSection.Name.StartsWith("Entry.", StringComparison.OrdinalIgnoreCase)) continue;
|
|
||||||
|
|
||||||
// Split the section name by '.' to identify parts
|
|
||||||
var nameParts = szfSection.Name.Split('.');
|
|
||||||
|
|
||||||
// Ensure it's a section of an entry (e.g., "Entry.EntryName.Section")
|
|
||||||
if (nameParts.Length < 3 || !nameParts[0].Equals("Entry", StringComparison.OrdinalIgnoreCase)) continue;
|
|
||||||
|
|
||||||
var entryName = nameParts[1];
|
|
||||||
if (!entriesByName.TryGetValue(entryName, out var targetEntry)) continue; // Entry not found, skip
|
|
||||||
|
|
||||||
// We need to build the hierarchy of DatasetSection objects
|
|
||||||
DatasetSection? currentParentSection = null;
|
|
||||||
List<DatasetSection> currentSectionList = targetEntry.Sections;
|
|
||||||
|
|
||||||
for (int i = 2; i < nameParts.Length; i++) // Start from the first section name after "Entry.EntryName"
|
|
||||||
{
|
{
|
||||||
var sectionNamePart = nameParts[i];
|
// Handle subsections by finding their parent entry
|
||||||
var existingSection = currentSectionList.FirstOrDefault(s =>
|
var parts = szfSection.Name.Split('.');
|
||||||
s.Name.Equals(sectionNamePart, StringComparison.OrdinalIgnoreCase));
|
if (parts.Length >= 2 && parts[0].Equals("Entry", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
parts[1].Equals(currentEntry.Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var subSection = new DatasetSection
|
||||||
|
{
|
||||||
|
Name = string.Join(".", parts.Skip(2)).Trim() // Name for the subsection
|
||||||
|
};
|
||||||
|
|
||||||
if (existingSection == null)
|
|
||||||
{
|
|
||||||
// Create new section
|
|
||||||
var newSection = new DatasetSection { Name = sectionNamePart };
|
|
||||||
currentSectionList.Add(newSection);
|
|
||||||
currentParentSection = newSection;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentParentSection = existingSection;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is the last part of the section name, populate its fields
|
|
||||||
if (i == nameParts.Length - 1)
|
|
||||||
{
|
|
||||||
foreach (var field in szfSection.Fields)
|
foreach (var field in szfSection.Fields)
|
||||||
{
|
{
|
||||||
currentParentSection.Fields.Add(ConvertToDatasetField(field));
|
subSection.Fields.Add(ConvertToDatasetField(field));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Move to the next level down in the hierarchy
|
currentEntry.Sections.Add(subSection);
|
||||||
currentSectionList = currentParentSection.Subsections;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert SzfField to DatasetField
|
|
||||||
/// </summary>
|
|
||||||
private DatasetField ConvertToDatasetField(SzfField szfField)
|
private DatasetField ConvertToDatasetField(SzfField szfField)
|
||||||
{
|
{
|
||||||
|
// You would typically parse more properties here like Description, IsRequired etc.
|
||||||
|
// For simplicity, only Name, Type and Value are mapped for now.
|
||||||
return new DatasetField
|
return new DatasetField
|
||||||
{
|
{
|
||||||
Name = szfField.Name,
|
Name = szfField.Name,
|
||||||
@ -121,9 +90,6 @@ public class Dataset : SzfObject
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse DatasetFieldType from string
|
|
||||||
/// </summary>
|
|
||||||
private DatasetFieldType ParseDatasetFieldType(string typeString)
|
private DatasetFieldType ParseDatasetFieldType(string typeString)
|
||||||
{
|
{
|
||||||
return typeString.ToLower() switch
|
return typeString.ToLower() switch
|
||||||
@ -132,38 +98,78 @@ public class Dataset : SzfObject
|
|||||||
"text-field" => DatasetFieldType.TextField,
|
"text-field" => DatasetFieldType.TextField,
|
||||||
"number" => DatasetFieldType.Number,
|
"number" => DatasetFieldType.Number,
|
||||||
"bool" => DatasetFieldType.Boolean,
|
"bool" => DatasetFieldType.Boolean,
|
||||||
"group" => DatasetFieldType.Group,
|
|
||||||
"calculated" => DatasetFieldType.Calculated,
|
"calculated" => DatasetFieldType.Calculated,
|
||||||
"system" => DatasetFieldType.System,
|
"system" => DatasetFieldType.System,
|
||||||
"dataset-reference" => DatasetFieldType.DatasetReference,
|
"dataset-reference" => DatasetFieldType.DatasetReference,
|
||||||
"dataset-type" => DatasetFieldType.DatasetType,
|
"dataset-type" => DatasetFieldType.DatasetType,
|
||||||
"dataset-reference-multiple" => DatasetFieldType.DatasetReferenceMultiple,
|
"dataset-reference-multiple" => DatasetFieldType.DatasetReferenceMultiple,
|
||||||
"dataset-type-multiple" => DatasetFieldType.DatasetTypeMultiple,
|
"dataset-type-multiple" => DatasetFieldType.DatasetTypeMultiple,
|
||||||
_ => DatasetFieldType.Text
|
"group" => DatasetFieldType.Group,
|
||||||
|
_ => DatasetFieldType.Text // Default to text if unknown
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void GenerateMetadata(SzfFieldWriter writer)
|
||||||
|
{
|
||||||
|
// Call base to ensure common metadata is generated first if base had any specific generation logic
|
||||||
|
base.GenerateMetadata(writer);
|
||||||
|
|
||||||
|
writer.WriteField("Type", "text", DatasetType);
|
||||||
|
if (!string.IsNullOrEmpty(ImageUrl))
|
||||||
|
writer.WriteField("ImageUrl", "text", ImageUrl);
|
||||||
|
|
||||||
|
// Write any other dynamic metadata
|
||||||
|
foreach (var kvp in Metadata)
|
||||||
|
{
|
||||||
|
writer.WriteField(kvp.Key, "text", kvp.Value); // Assuming text for generic metadata for now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GenerateContent(SzfFieldWriter writer)
|
||||||
|
{
|
||||||
|
foreach (var entry in Entries)
|
||||||
|
{
|
||||||
|
writer.AppendSectionHeader($"Entry: {entry.Name}");
|
||||||
|
|
||||||
|
foreach (var field in entry.Fields)
|
||||||
|
{
|
||||||
|
writer.WriteField(field.Name, field.Type, field.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var section in entry.Sections)
|
||||||
|
{
|
||||||
|
GenerateDatasetSection(writer, section, $"Entry.{entry.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateDatasetSection(SzfFieldWriter writer, DatasetSection section, string parentPath)
|
||||||
|
{
|
||||||
|
var sectionPath = $"{parentPath}.{section.Name}";
|
||||||
|
writer.AppendSectionHeader(sectionPath);
|
||||||
|
|
||||||
|
foreach (var field in section.Fields)
|
||||||
|
{
|
||||||
|
writer.WriteField(field.Name, field.Type, field.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var subsection in section.Subsections)
|
||||||
|
{
|
||||||
|
GenerateDatasetSection(writer, subsection, sectionPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override SzfValidationResult Validate()
|
public override SzfValidationResult Validate()
|
||||||
{
|
{
|
||||||
var result = base.Validate();
|
var result = base.Validate();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(DatasetType))
|
if (string.IsNullOrWhiteSpace(DatasetType))
|
||||||
result.AddError("DatasetType is required");
|
result.AddError("DatasetType is required for Dataset");
|
||||||
|
|
||||||
// Validate entries
|
if (!Entries.Any())
|
||||||
var entryNames = new HashSet<string>();
|
result.AddWarning("Dataset contains no entries.");
|
||||||
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;
|
return result;
|
||||||
}
|
}
|
||||||
@ -172,36 +178,30 @@ public class Dataset : SzfObject
|
|||||||
public class DatasetEntry
|
public class DatasetEntry
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public List<DatasetField> Fields { get; set; } = [];
|
public List<DatasetField> Fields { get; set; } = new();
|
||||||
public List<DatasetSection> Sections { get; set; } = [];
|
public List<DatasetSection> Sections { get; set; } = new(); // For nested sections within an entry
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DatasetSection
|
public class DatasetSection
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public List<DatasetField> Fields { get; set; } = [];
|
public List<DatasetField> Fields { get; set; } = new();
|
||||||
public List<DatasetSection> Subsections { get; set; } = [];
|
public List<DatasetSection> Subsections { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DatasetField
|
public class DatasetField
|
||||||
{
|
{
|
||||||
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; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public bool IsRequired { get; set; } = false;
|
public bool IsRequired { get; set; }
|
||||||
public object? DefaultValue { get; set; }
|
public object? DefaultValue { get; set; }
|
||||||
|
|
||||||
// For calculated fields
|
|
||||||
public string? Formula { get; set; }
|
public string? Formula { get; set; }
|
||||||
|
public string? ReferencedDatasetId { get; set; } // For dataset-reference types, stores the ID
|
||||||
// For dataset reference fields
|
public string? ReferencedDatasetType { get; set; } // For dataset-type types, stores the type
|
||||||
public string? ReferencedDatasetId { get; set; }
|
public bool AllowMultiple { get; set; }
|
||||||
public string? ReferencedDatasetType { get; set; }
|
public List<DatasetField> GroupFields { get; set; } = new(); // For group fields
|
||||||
public bool AllowMultiple { get; set; } = false;
|
|
||||||
|
|
||||||
// For group fields
|
|
||||||
public List<DatasetField> GroupFields { get; set; } = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DatasetFieldType
|
public enum DatasetFieldType
|
||||||
@ -210,42 +210,50 @@ public enum DatasetFieldType
|
|||||||
TextField,
|
TextField,
|
||||||
Number,
|
Number,
|
||||||
Boolean,
|
Boolean,
|
||||||
Group,
|
|
||||||
Calculated,
|
Calculated,
|
||||||
System,
|
System,
|
||||||
DatasetReference,
|
DatasetReference,
|
||||||
DatasetType,
|
DatasetType,
|
||||||
DatasetReferenceMultiple,
|
DatasetReferenceMultiple,
|
||||||
DatasetTypeMultiple
|
DatasetTypeMultiple,
|
||||||
|
Group
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supporting classes for dataset management
|
|
||||||
public class DatasetReference
|
public class DatasetReference
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public Guid Guid { get; set; }
|
public Guid Guid { get; set; }
|
||||||
public string Version { get; set; } = string.Empty;
|
public string Version { get; set; } = "1.0.0";
|
||||||
|
|
||||||
public override string ToString() => $"{Name}|{Guid}|{Version}";
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Name}|{Guid}|{Version}";
|
||||||
|
}
|
||||||
|
|
||||||
public static DatasetReference? Parse(string reference)
|
public static DatasetReference? Parse(string reference)
|
||||||
{
|
{
|
||||||
var parts = reference.Split('|');
|
if (string.IsNullOrWhiteSpace(reference))
|
||||||
if (parts.Length != 3 || !Guid.TryParse(parts[1], out var id))
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new DatasetReference
|
var parts = reference.Split('|');
|
||||||
|
if (parts.Length != 3)
|
||||||
|
return null; // Invalid format
|
||||||
|
|
||||||
|
if (Guid.TryParse(parts[1], out var guid))
|
||||||
{
|
{
|
||||||
Name = parts[0],
|
return new DatasetReference
|
||||||
Guid = id,
|
{
|
||||||
Version = parts[2]
|
Name = parts[0],
|
||||||
};
|
Guid = guid,
|
||||||
|
Version = parts[2]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // Invalid GUID format
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DatasetValidationResult
|
public class DatasetValidationResult : SzfValidationResult
|
||||||
{
|
{
|
||||||
public bool IsValid { get; set; }
|
// Specific validation results for datasets can go here if needed
|
||||||
public List<string> Errors { get; set; } = [];
|
|
||||||
public List<string> Warnings { get; set; } = [];
|
|
||||||
}
|
}
|
98
SessionZero/Data/SzfFieldWriter.cs
Normal file
98
SessionZero/Data/SzfFieldWriter.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// SzfFieldWriter.cs
|
||||||
|
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SessionZero.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class to write fields to a StringBuilder following SZF format,
|
||||||
|
/// respecting generator options like indentation.
|
||||||
|
/// </summary>
|
||||||
|
public class SzfFieldWriter
|
||||||
|
{
|
||||||
|
public readonly StringBuilder Builder;
|
||||||
|
public readonly SzfGeneratorOptions Options;
|
||||||
|
|
||||||
|
public SzfFieldWriter(StringBuilder builder, SzfGeneratorOptions options)
|
||||||
|
{
|
||||||
|
Builder = builder;
|
||||||
|
Options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a generic field with a string type.
|
||||||
|
/// </summary>
|
||||||
|
public void WriteField(string name, string type, object? value)
|
||||||
|
{
|
||||||
|
if (!Options.IncludeEmptyFields && (value == null || string.IsNullOrEmpty(value.ToString())))
|
||||||
|
{
|
||||||
|
return; // Skip empty fields if option is set
|
||||||
|
}
|
||||||
|
|
||||||
|
var formattedValue = FormatFieldValue(value, type);
|
||||||
|
|
||||||
|
if (Options.IndentFields)
|
||||||
|
{
|
||||||
|
Builder.AppendLine($"{Options.Indent}{name} ({type}) = {formattedValue}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Builder.AppendLine($"{name} ({type}) = {formattedValue}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a field with a DatasetFieldType.
|
||||||
|
/// </summary>
|
||||||
|
public void WriteField(string name, DatasetFieldType type, object? value)
|
||||||
|
{
|
||||||
|
var typeString = 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" // Default or unknown types
|
||||||
|
};
|
||||||
|
WriteField(name, typeString, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats field values based on their declared type for SZF output.
|
||||||
|
/// </summary>
|
||||||
|
private string FormatFieldValue(object? value, string fieldType)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return fieldType.ToLower() switch
|
||||||
|
{
|
||||||
|
"bool" => (bool)value ? "true" : "false",
|
||||||
|
"number" => value.ToString() ?? "0",
|
||||||
|
_ => value.ToString() ?? string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a new line to the string builder.
|
||||||
|
/// </summary>
|
||||||
|
public void AppendLine()
|
||||||
|
{
|
||||||
|
Builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a section header to the string builder.
|
||||||
|
/// </summary>
|
||||||
|
public void AppendSectionHeader(string header)
|
||||||
|
{
|
||||||
|
Builder.AppendLine($"[{header}]");
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace SessionZero.Data;
|
namespace SessionZero.Data;
|
||||||
@ -17,15 +18,16 @@ public class SzfGenerator
|
|||||||
public string Generate(SzfObject obj)
|
public string Generate(SzfObject obj)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
var writer = new SzfFieldWriter(builder, _options);
|
||||||
|
|
||||||
// Generate header
|
// Generate header
|
||||||
GenerateHeader(builder, obj);
|
GenerateHeader(writer, obj);
|
||||||
|
|
||||||
// Generate metadata section
|
// Generate metadata section
|
||||||
GenerateMetadataSection(builder, obj);
|
GenerateMetadataSection(writer, obj);
|
||||||
|
|
||||||
// Generate type-specific content
|
// Generate type-specific content
|
||||||
GenerateTypeSpecificContent(builder, obj);
|
GenerateTypeSpecificContent(writer, obj);
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
@ -33,179 +35,48 @@ public class SzfGenerator
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate the SZF header (!type: and !schema:)
|
/// Generate the SZF header (!type: and !schema:)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void GenerateHeader(StringBuilder builder, SzfObject obj)
|
private void GenerateHeader(SzfFieldWriter writer, SzfObject obj)
|
||||||
{
|
{
|
||||||
var objectTypeString = obj.ObjectType switch
|
var type = obj.GetType();
|
||||||
{
|
var attribute = type.GetCustomAttribute<SzfObjectAttribute>();
|
||||||
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}");
|
if (attribute == null)
|
||||||
builder.AppendLine($"!schema: {obj.SchemaVersion}");
|
{
|
||||||
builder.AppendLine();
|
throw new SzfGenerateException(
|
||||||
|
$"Type {type.Name} does not have SzfObjectAttribute, cannot determine type string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Builder.AppendLine($"!type: {attribute.TypeString}");
|
||||||
|
writer.Builder.AppendLine($"!schema: {obj.SchemaVersion}");
|
||||||
|
writer.Builder.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate the common metadata section
|
/// Generate the common metadata section
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void GenerateMetadataSection(StringBuilder builder, SzfObject obj)
|
private void GenerateMetadataSection(SzfFieldWriter writer, SzfObject obj)
|
||||||
{
|
{
|
||||||
builder.AppendLine("[Metadata]");
|
writer.AppendSectionHeader("Metadata");
|
||||||
|
|
||||||
// Standard metadata fields
|
// Standard metadata fields
|
||||||
WriteField(builder, "Name", "text", obj.Name);
|
writer.WriteField("Name", "text", obj.Name);
|
||||||
WriteField(builder, "Version", "text", obj.Version);
|
writer.WriteField("Version", "text", obj.Version);
|
||||||
WriteField(builder, "GUID", "text", obj.Id.ToString());
|
writer.WriteField("GUID", "text", obj.Id.ToString());
|
||||||
WriteField(builder, "Description", "text-field", obj.Description);
|
writer.WriteField("Description", "text-field", obj.Description);
|
||||||
WriteField(builder, "Author", "text", obj.Author);
|
writer.WriteField("Author", "text", obj.Author);
|
||||||
|
|
||||||
// Type-specific metadata
|
// Type-specific metadata delegated to the object itself
|
||||||
GenerateTypeSpecificMetadata(builder, obj);
|
obj.GenerateMetadata(writer);
|
||||||
|
|
||||||
builder.AppendLine();
|
writer.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate type-specific metadata fields
|
/// Generate type-specific content sections delegated to the object itself
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void GenerateTypeSpecificMetadata(StringBuilder builder, SzfObject obj)
|
private void GenerateTypeSpecificContent(SzfFieldWriter writer, SzfObject obj)
|
||||||
{
|
{
|
||||||
switch (obj)
|
obj.GenerateContent(writer);
|
||||||
{
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
// SzfObject.cs
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace SessionZero.Data;
|
namespace SessionZero.Data;
|
||||||
|
|
||||||
public abstract class SzfObject
|
public abstract class SzfObject
|
||||||
@ -22,6 +27,26 @@ public abstract class SzfObject
|
|||||||
/// <param name="sections">A list of all parsed sections from the SZF file.</param>
|
/// <param name="sections">A list of all parsed sections from the SZF file.</param>
|
||||||
public abstract void ParseSections(List<SzfSection> sections);
|
public abstract void ParseSections(List<SzfSection> sections);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Virtual method for SzfObjects to generate their specific metadata fields.
|
||||||
|
/// Override this in derived classes to add custom metadata to the [Metadata] section.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The SzfFieldWriter instance to write fields.</param>
|
||||||
|
public virtual void GenerateMetadata(SzfFieldWriter writer)
|
||||||
|
{
|
||||||
|
// Default implementation does nothing, derived classes can override.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Virtual method for SzfObjects to generate their specific content sections.
|
||||||
|
/// Override this in derived classes to add custom sections and fields.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The SzfFieldWriter instance to write fields and manage sections.</param>
|
||||||
|
public virtual void GenerateContent(SzfFieldWriter writer)
|
||||||
|
{
|
||||||
|
// Default implementation does nothing, derived classes can override.
|
||||||
|
}
|
||||||
|
|
||||||
public virtual SzfValidationResult Validate()
|
public virtual SzfValidationResult Validate()
|
||||||
{
|
{
|
||||||
var result = new SzfValidationResult { IsValid = true };
|
var result = new SzfValidationResult { IsValid = true };
|
||||||
|
@ -3,20 +3,26 @@
|
|||||||
@using Microsoft.Extensions.Logging
|
@using Microsoft.Extensions.Logging
|
||||||
@inject SzfParser SzfParser
|
@inject SzfParser SzfParser
|
||||||
@inject ILogger<SzfParser> Logger
|
@inject ILogger<SzfParser> Logger
|
||||||
|
@inject SzfGenerator SzfGenerator
|
||||||
|
|
||||||
<h1>SZF Parser Test Page</h1>
|
<h1>SZF Parser and Generator Test Page</h1>
|
||||||
|
|
||||||
<div>
|
<div class="test-container">
|
||||||
<div>
|
<div class="input-section">
|
||||||
|
<h2>SZF Input</h2>
|
||||||
<div>
|
<div>
|
||||||
<textarea id="szf-input" @bind="szfCode" rows="20"></textarea>
|
<textarea id="szf-input" @bind="szfCode" rows="20"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button @onclick="ParseSzf">Parse SZF</button>
|
<button @onclick="ParseSzf">Parse SZF</button>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
@if (parsedObject != null)
|
@if (parsedObject != null)
|
||||||
{
|
{
|
||||||
<h2>Parsed Object Data</h2>
|
<button @onclick="GenerateSzf">Generate SZF from Parsed Object</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="output-section">
|
||||||
|
<h2>Parsed Object Data</h2>
|
||||||
|
@if (parsedObject != null)
|
||||||
|
{
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><th>Object Type</th><td>@parsedObject.ObjectType</td></tr>
|
<tr><th>Object Type</th><td>@parsedObject.ObjectType</td></tr>
|
||||||
@ -105,7 +111,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>@reqDataset.Alias</td>
|
<td>@reqDataset.Alias</td>
|
||||||
<td>@reqDataset.Reference?.Name</td>
|
<td>@reqDataset.Reference?.Name</td>
|
||||||
<td>@reqDataset.Reference?.Guid</td> @* Changed from DatasetId to Guid *@
|
<td>@reqDataset.Reference?.Guid</td>
|
||||||
<td>@reqDataset.Reference?.Version</td>
|
<td>@reqDataset.Reference?.Version</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@ -136,25 +142,81 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="generated-output-section">
|
||||||
|
<h2>Generated SZF Output</h2>
|
||||||
|
@if (!string.IsNullOrEmpty(generatedSzfOutput))
|
||||||
|
{
|
||||||
|
<textarea id="generated-szf-output" @bind="generatedSzfOutput" rows="20" readonly></textarea>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>No SZF has been generated yet.</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="logs-section">
|
||||||
<h2>Logs</h2>
|
<h2>Logs</h2>
|
||||||
<pre>@string.Join("\n", logMessages)</pre>
|
<pre>@string.Join("\n", logMessages)</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.test-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr; /* Three columns for input, parsed data, generated output */
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-section, .output-section, .generated-output-section {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box; /* Include padding and border in the element's total width and height */
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-section {
|
||||||
|
margin-top: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap; /* Ensures logs wrap */
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string szfCode = string.Empty;
|
private string szfCode = string.Empty;
|
||||||
private SzfObject? parsedObject;
|
private SzfObject? parsedObject;
|
||||||
|
private string generatedSzfOutput = string.Empty; // New field for generated output
|
||||||
private List<string> logMessages = new List<string>();
|
private List<string> logMessages = new List<string>();
|
||||||
private List<string> errorMessages = new List<string>();
|
private List<string> errorMessages = new List<string>();
|
||||||
|
|
||||||
private void ParseSzf()
|
private void ParseSzf()
|
||||||
{
|
{
|
||||||
logMessages.Clear();
|
ClearState(); // Clear previous states
|
||||||
errorMessages.Clear();
|
|
||||||
parsedObject = null;
|
|
||||||
|
|
||||||
Log("Starting SZF parsing...");
|
Log("Starting SZF parsing...");
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(szfCode))
|
if (string.IsNullOrWhiteSpace(szfCode))
|
||||||
@ -180,6 +242,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GenerateSzf()
|
||||||
|
{
|
||||||
|
generatedSzfOutput = string.Empty; // Clear previous generated output
|
||||||
|
if (parsedObject == null)
|
||||||
|
{
|
||||||
|
Log("No object to generate from. Please parse an SZF first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("Starting SZF generation...");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use the ToSzfString() method on the parsed object, which uses SzfGenerator internally
|
||||||
|
generatedSzfOutput = parsedObject.ToSzfString();
|
||||||
|
Log("SZF content generated successfully.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
const string message = "An error occurred during generation.";
|
||||||
|
LogError(message, ex);
|
||||||
|
errorMessages.Add($"{message} See logs for details.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearState()
|
||||||
|
{
|
||||||
|
logMessages.Clear();
|
||||||
|
errorMessages.Clear();
|
||||||
|
parsedObject = null;
|
||||||
|
generatedSzfOutput = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
private void Log(string message)
|
private void Log(string message)
|
||||||
{
|
{
|
||||||
Logger.LogInformation(message);
|
Logger.LogInformation(message);
|
3
SessionZero/Pages/SzfParseTest.razor.css
Normal file
3
SessionZero/Pages/SzfParseTest.razor.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.main-content {
|
||||||
|
text-align: left; !important;
|
||||||
|
}
|
@ -15,6 +15,7 @@ builder.Services.AddBlazoredLocalStorage();
|
|||||||
|
|
||||||
// Register our services
|
// Register our services
|
||||||
builder.Services.AddScoped<SzfParser>();
|
builder.Services.AddScoped<SzfParser>();
|
||||||
|
builder.Services.AddScoped<SzfGenerator>();
|
||||||
|
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
await builder.Build().RunAsync();
|
Loading…
Reference in New Issue
Block a user