updating the szf format and parsers to be more consistent
This commit is contained in:
parent
d89774e8fc
commit
7f15c4c8ba
@ -46,13 +46,13 @@
|
||||
<select>
|
||||
<option value="" disabled selected>Field Type</option>
|
||||
<option value="text">Text</option>
|
||||
<option value="text-field">Multi-Line Text</option>
|
||||
<option value="number">Number</option>
|
||||
<option value="calculated">Formula</option>
|
||||
<option value="boolean">Boolean</option>
|
||||
<option value="entry-reference">Entry Reference</option>
|
||||
<option value="entry-reference-multi">Multi Entry Reference</option>
|
||||
<option value="system">System</option>
|
||||
<option value="calculated">Formula</option>
|
||||
<option value="text-field">Multi-Line Text</option>
|
||||
@* <option value="entry-reference">Entry Reference</option> *@
|
||||
@* <option value="entry-reference-multi">Multi Entry Reference</option> *@
|
||||
@* <option value="system">System</option> *@
|
||||
</select>
|
||||
|
||||
<input type="text" placeholder="Field Value"/>
|
||||
|
@ -22,6 +22,7 @@ public class CharacterSection
|
||||
[SzfObject("character")]
|
||||
public class Character : SzfObject
|
||||
{
|
||||
public string TemplateName { get; private set; } = "Unknown";
|
||||
public Guid TemplateGuid { get; private set; }
|
||||
public Version TemplateVersion { get; private set; } = new Version(1, 0, 0);
|
||||
|
||||
@ -59,125 +60,114 @@ public class Character : SzfObject
|
||||
base.PopulateFromMetadata(metadata);
|
||||
|
||||
// Populate Character-specific metadata from the "Metadata" section
|
||||
if (metadata.TryGetValue("SourceTemplateGuid", out var guidField))
|
||||
if (metadata.TryGetValue("TemplateRef", out var refField) && refField.Value is string refValue)
|
||||
{
|
||||
if (Guid.TryParse(guidField.Value?.ToString(), out var guid))
|
||||
var parts = refValue.Split('|');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
TemplateGuid = guid;
|
||||
TemplateName = parts[0];
|
||||
if (Guid.TryParse(parts[1], out var guid))
|
||||
{
|
||||
TemplateGuid = guid;
|
||||
}
|
||||
|
||||
if (System.Version.TryParse(parts[2], out var ver))
|
||||
{
|
||||
TemplateVersion = ver;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.TryGetValue("SourceTemplateVersion", out var versionField))
|
||||
// Fallback for legacy format
|
||||
else
|
||||
{
|
||||
// Ensure the value is converted to a string and explicitly call System.Version.TryParse
|
||||
if (System.Version.TryParse(Convert.ToString(versionField.Value), out var ver))
|
||||
if (metadata.TryGetValue("SourceTemplateGuid", out var guidField))
|
||||
{
|
||||
TemplateVersion = ver;
|
||||
if (Guid.TryParse(guidField.Value?.ToString(), out var guid))
|
||||
{
|
||||
TemplateGuid = guid;
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.TryGetValue("SourceTemplateVersion", out var versionField))
|
||||
{
|
||||
// Ensure the value is converted to a string and explicitly call System.Version.TryParse
|
||||
if (System.Version.TryParse(Convert.ToString(versionField.Value), out var ver))
|
||||
{
|
||||
TemplateVersion = ver;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ParseSections(List<SzfSection> sections)
|
||||
{
|
||||
CharacterSections.Clear(); // Clear existing sections to ensure a fresh parse
|
||||
|
||||
// A temporary list to hold top-level CharacterSections as we parse
|
||||
var tempTopLevelCharacterSections = new List<CharacterSection>();
|
||||
|
||||
// A map to quickly find parent sections by their name (simplified for lookup)
|
||||
// e.g., "Character Information" -> CharacterSection object
|
||||
var sectionNameMap = new Dictionary<string, CharacterSection>(StringComparer.OrdinalIgnoreCase);
|
||||
CharacterSections.Clear();
|
||||
var sectionMap = new Dictionary<string, CharacterSection>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// This implementation assumes that parent sections are defined in the file
|
||||
// before any of their subsections.
|
||||
foreach (var szfSection in sections)
|
||||
{
|
||||
if (szfSection.Name.Equals("Metadata", StringComparison.OrdinalIgnoreCase))
|
||||
var name = szfSection.Name;
|
||||
|
||||
// Metadata is handled by PopulateFromMetadata, so skip here
|
||||
if (name.Equals("Metadata", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Metadata is handled by PopulateFromMetadata, so skip here
|
||||
continue;
|
||||
}
|
||||
|
||||
if (szfSection.Name.StartsWith("Section:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// This is a top-level section (e.g., [Section: Character Information])
|
||||
var sectionName = szfSection.Name.Replace("Section:", "").Trim();
|
||||
var characterSection = new CharacterSection { Name = sectionName };
|
||||
// --- Handle Subsections ---
|
||||
var nameParts = name.Split('.');
|
||||
string? parentName = null;
|
||||
string? childName = null;
|
||||
|
||||
// New format: [ParentName.ChildName]
|
||||
if (nameParts.Length > 1 && !nameParts[0].Equals("Section", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
parentName = nameParts[0];
|
||||
childName = string.Join(".", nameParts.Skip(1));
|
||||
}
|
||||
// Legacy format: [Section.ParentName.ChildName]
|
||||
else if (nameParts.Length > 2 && nameParts[0].Equals("Section", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
parentName = nameParts[1];
|
||||
childName = string.Join(".", nameParts.Skip(2));
|
||||
}
|
||||
|
||||
if (parentName != null && childName != null && sectionMap.TryGetValue(parentName, out var parentSection))
|
||||
{
|
||||
var subSection = new CharacterSection { Name = childName };
|
||||
foreach (var field in szfSection.Fields)
|
||||
{
|
||||
characterSection.Fields.Add(new CharacterField
|
||||
{
|
||||
Name = field.Name,
|
||||
Type = field.Type, // Assign directly as string
|
||||
Value = field.Value
|
||||
});
|
||||
subSection.Fields.Add(new CharacterField
|
||||
{ Name = field.Name, Type = field.Type, Value = field.Value });
|
||||
}
|
||||
|
||||
tempTopLevelCharacterSections.Add(characterSection);
|
||||
sectionNameMap[sectionName] = characterSection; // Store by its simplified name for easy lookup
|
||||
parentSection.Subsections.Add(subSection);
|
||||
}
|
||||
else if (szfSection.Name.StartsWith("Section.", StringComparison.OrdinalIgnoreCase))
|
||||
else // --- Handle Top-Level Sections ---
|
||||
{
|
||||
// This is a subsection (e.g., [Section.Ability Scores.Modifiers])
|
||||
var parts = szfSection.Name.Split('.');
|
||||
if (parts.Length < 2) continue; // Malformed section name
|
||||
|
||||
// The path to the immediate parent (e.g., "Ability Scores" for "Ability Scores.Modifiers")
|
||||
var parentPathSegments = new List<string>();
|
||||
// Start from the first segment after "Section." and go up to the segment before the current section's name
|
||||
for (int i = 1; i < parts.Length - 1; i++)
|
||||
var sectionName = name;
|
||||
// Legacy format: [Section:SectionName]
|
||||
if (sectionName.StartsWith("Section:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
parentPathSegments.Add(parts[i]);
|
||||
sectionName = sectionName.Substring("Section:".Length).Trim();
|
||||
}
|
||||
|
||||
var parentLookupKey = string.Join(".", parentPathSegments);
|
||||
|
||||
// Find the direct parent section within the already processed hierarchy
|
||||
CharacterSection? parentSection =
|
||||
FindCharacterSectionByPath(tempTopLevelCharacterSections, parentLookupKey);
|
||||
|
||||
if (parentSection != null)
|
||||
if (!sectionMap.ContainsKey(sectionName))
|
||||
{
|
||||
var subSection = new CharacterSection
|
||||
{
|
||||
Name = parts.Last().Trim() // The last part of the name is the subsection's own name
|
||||
};
|
||||
|
||||
var characterSection = new CharacterSection { Name = sectionName };
|
||||
foreach (var field in szfSection.Fields)
|
||||
{
|
||||
subSection.Fields.Add(new CharacterField
|
||||
{
|
||||
Name = field.Name,
|
||||
Type = field.Type, // Assign directly as string
|
||||
Value = field.Value
|
||||
});
|
||||
characterSection.Fields.Add(new CharacterField
|
||||
{ Name = field.Name, Type = field.Type, Value = field.Value });
|
||||
}
|
||||
|
||||
parentSection.Subsections.Add(subSection);
|
||||
CharacterSections.Add(characterSection);
|
||||
sectionMap[sectionName] = characterSection;
|
||||
}
|
||||
// If parent not found, it implies an issue with SZF structure or order.
|
||||
// For now, we silently skip it. In a robust system, you might log an error.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle any other root-level sections that are not "Metadata"
|
||||
// and don't conform to the "Section:..." or "Section...." patterns.
|
||||
// These are treated as flat character sections.
|
||||
var characterSection = new CharacterSection { Name = szfSection.Name.Trim() };
|
||||
foreach (var field in szfSection.Fields)
|
||||
{
|
||||
characterSection.Fields.Add(new CharacterField
|
||||
{
|
||||
Name = field.Name,
|
||||
Type = field.Type, // Assign directly as string
|
||||
Value = field.Value
|
||||
});
|
||||
}
|
||||
|
||||
tempTopLevelCharacterSections.Add(characterSection);
|
||||
}
|
||||
}
|
||||
|
||||
CharacterSections.AddRange(tempTopLevelCharacterSections);
|
||||
}
|
||||
|
||||
// Helper method to find a section by its hierarchical path (e.g., "ParentName.SubName")
|
||||
@ -208,8 +198,7 @@ public class Character : SzfObject
|
||||
base.GenerateMetadata(writer);
|
||||
|
||||
// Write Character-specific metadata fields
|
||||
writer.WriteField("SourceTemplateGuid", "text", TemplateGuid.ToString());
|
||||
writer.WriteField("SourceTemplateVersion", "text", TemplateVersion.ToString());
|
||||
writer.WriteField("TemplateRef", "text", $"{TemplateName}|{TemplateGuid}|{TemplateVersion}");
|
||||
}
|
||||
|
||||
public override void GenerateContent(SzfFieldWriter writer)
|
||||
|
@ -20,66 +20,82 @@ public class CharacterTemplate : SzfObject
|
||||
|
||||
public override void ParseSections(List<SzfSection> sections)
|
||||
{
|
||||
// Parse [Required Datasets] section
|
||||
var requiredDatasetsSection =
|
||||
sections.FirstOrDefault(s => s.Name.Equals("Required Datasets", StringComparison.OrdinalIgnoreCase));
|
||||
if (requiredDatasetsSection != null)
|
||||
{
|
||||
foreach (var field in requiredDatasetsSection.Fields)
|
||||
{
|
||||
var reference = DatasetReference.Parse(field.Value?.ToString() ?? string.Empty);
|
||||
if (reference != null)
|
||||
{
|
||||
RequiredDatasets.Add(new RequiredDatasetReference
|
||||
{
|
||||
Alias = field.Name,
|
||||
Reference = reference
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Sections.Clear();
|
||||
this.RequiredDatasets.Clear();
|
||||
var sectionMap = new Dictionary<string, TemplateSection>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Parse [Section: ...] and [Section....] sections
|
||||
// This implementation assumes that parent sections are defined in the file
|
||||
// before any of their subsections.
|
||||
foreach (var szfSection in sections)
|
||||
{
|
||||
if (szfSection.Name.StartsWith("Section:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var templateSection = new TemplateSection
|
||||
{
|
||||
Name = szfSection.Name.Replace("Section:", "").Trim()
|
||||
};
|
||||
var name = szfSection.Name;
|
||||
|
||||
// --- Handle Special "Required Datasets" Section ---
|
||||
if (name.Equals("Required Datasets", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (var field in szfSection.Fields)
|
||||
{
|
||||
templateSection.Fields.Add(ConvertToTemplateField(field));
|
||||
var reference = DatasetReference.Parse(field.Value?.ToString() ?? string.Empty);
|
||||
if (reference != null)
|
||||
{
|
||||
RequiredDatasets.Add(new RequiredDatasetReference
|
||||
{
|
||||
Alias = field.Name,
|
||||
Reference = reference
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Sections.Add(templateSection);
|
||||
continue; // Processed, move to next section
|
||||
}
|
||||
else if (szfSection.Name.StartsWith("Section.", StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
// --- Handle Subsections ---
|
||||
var nameParts = name.Split('.');
|
||||
string? parentName = null;
|
||||
string? childName = null;
|
||||
|
||||
// New format: [ParentName.ChildName]
|
||||
if (nameParts.Length > 1 && !nameParts[0].Equals("Section", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Handle subsections by finding their parent
|
||||
var parts = szfSection.Name.Split('.');
|
||||
if (parts.Length >= 2)
|
||||
parentName = nameParts[0];
|
||||
childName = string.Join(".", nameParts.Skip(1));
|
||||
}
|
||||
// Legacy format: [Section.ParentName.ChildName]
|
||||
else if (nameParts.Length > 2 && nameParts[0].Equals("Section", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
parentName = nameParts[1];
|
||||
childName = string.Join(".", nameParts.Skip(2));
|
||||
}
|
||||
|
||||
if (parentName != null && childName != null && sectionMap.TryGetValue(parentName, out var parentSection))
|
||||
{
|
||||
var subSection = new TemplateSection { Name = childName };
|
||||
foreach (var field in szfSection.Fields)
|
||||
{
|
||||
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)
|
||||
subSection.Fields.Add(ConvertToTemplateField(field));
|
||||
}
|
||||
|
||||
parentSection.Subsections.Add(subSection);
|
||||
}
|
||||
else // --- Handle Top-Level Sections ---
|
||||
{
|
||||
var sectionName = name;
|
||||
// Legacy format: [Section:SectionName]
|
||||
if (sectionName.StartsWith("Section:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sectionName = sectionName.Substring("Section:".Length).Trim();
|
||||
}
|
||||
|
||||
if (!sectionMap.ContainsKey(sectionName))
|
||||
{
|
||||
var templateSection = new TemplateSection { Name = sectionName };
|
||||
foreach (var field in szfSection.Fields)
|
||||
{
|
||||
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);
|
||||
templateSection.Fields.Add(ConvertToTemplateField(field));
|
||||
}
|
||||
|
||||
this.Sections.Add(templateSection);
|
||||
sectionMap[sectionName] = templateSection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,42 +36,67 @@ public class Dataset : SzfObject
|
||||
|
||||
public override void ParseSections(List<SzfSection> sections)
|
||||
{
|
||||
var currentEntry = (DatasetEntry?)null;
|
||||
// This implementation assumes that parent entries are defined in the file
|
||||
// before any of their subsections.
|
||||
Entries.Clear();
|
||||
var entryMap = new Dictionary<string, DatasetEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var szfSection in sections)
|
||||
{
|
||||
if (szfSection.Name.StartsWith("Entry:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
currentEntry = new DatasetEntry
|
||||
{
|
||||
Name = szfSection.Name.Replace("Entry:", "").Trim()
|
||||
};
|
||||
var name = szfSection.Name;
|
||||
|
||||
// --- Check for Subsections ---
|
||||
// Handles new format `[EntryName.SubSection]` and legacy `[Entry.EntryName.SubSection]`
|
||||
var nameParts = name.Split('.');
|
||||
string? parentName = null;
|
||||
string? subSectionName = null;
|
||||
|
||||
if (nameParts.Length > 1)
|
||||
{
|
||||
if (nameParts[0].Equals("Entry", StringComparison.OrdinalIgnoreCase) && nameParts.Length > 2)
|
||||
{
|
||||
// Legacy: [Entry.EntryName.SubSection]
|
||||
parentName = nameParts[1];
|
||||
subSectionName = string.Join(".", nameParts.Skip(2));
|
||||
}
|
||||
else if (!nameParts[0].Equals("Entry", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// New: [EntryName.SubSection]
|
||||
parentName = nameParts[0];
|
||||
subSectionName = string.Join(".", nameParts.Skip(1));
|
||||
}
|
||||
}
|
||||
|
||||
if (parentName != null && subSectionName != null && entryMap.TryGetValue(parentName, out var parentEntry))
|
||||
{
|
||||
var subSection = new DatasetSection { Name = subSectionName };
|
||||
foreach (var field in szfSection.Fields)
|
||||
{
|
||||
currentEntry.Fields.Add(ConvertToDatasetField(field));
|
||||
subSection.Fields.Add(ConvertToDatasetField(field));
|
||||
}
|
||||
|
||||
Entries.Add(currentEntry);
|
||||
parentEntry.Sections.Add(subSection);
|
||||
}
|
||||
else if (szfSection.Name.StartsWith("Entry.", StringComparison.OrdinalIgnoreCase) && currentEntry != null)
|
||||
else // --- Handle as Top-Level Entry ---
|
||||
{
|
||||
// Handle subsections by finding their parent entry
|
||||
var parts = szfSection.Name.Split('.');
|
||||
if (parts.Length >= 2 && parts[0].Equals("Entry", StringComparison.OrdinalIgnoreCase) &&
|
||||
parts[1].Equals(currentEntry.Name, StringComparison.OrdinalIgnoreCase))
|
||||
var entryName = name;
|
||||
// Legacy: [Entry:EntryName]
|
||||
if (entryName.StartsWith("Entry:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var subSection = new DatasetSection
|
||||
{
|
||||
Name = string.Join(".", parts.Skip(2)).Trim() // Name for the subsection
|
||||
};
|
||||
entryName = entryName.Substring("Entry:".Length).Trim();
|
||||
}
|
||||
|
||||
foreach (var field in szfSection.Fields)
|
||||
{
|
||||
subSection.Fields.Add(ConvertToDatasetField(field));
|
||||
}
|
||||
if (!entryMap.TryGetValue(entryName, out var entry))
|
||||
{
|
||||
entry = new DatasetEntry { Name = entryName };
|
||||
Entries.Add(entry);
|
||||
entryMap[entryName] = entry;
|
||||
}
|
||||
|
||||
currentEntry.Sections.Add(subSection);
|
||||
// Add fields to the entry
|
||||
foreach (var field in szfSection.Fields)
|
||||
{
|
||||
entry.Fields.Add(ConvertToDatasetField(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ A dataset file is a collection of structured entries.
|
||||
* Type (text): The category of the dataset (e.g., items, spells, classes).
|
||||
* Guid (text): A globally unique identifier. **Optional**: If left blank, the system can generate one.
|
||||
* Version (text): The semantic version of the dataset.
|
||||
* **[Entry:EntryName] Section**: Defines a single item in the dataset.
|
||||
* **[EntryName] Section**: Defines a single item in the dataset.
|
||||
* EntryName is the unique key for the entry within the dataset.
|
||||
|
||||
**Example: CoreItems.szf**
|
||||
@ -121,12 +121,12 @@ Author (text) = Fantasy Creator
|
||||
Description (text-field) = A collection of basic items for any fantasy campaign.
|
||||
|
||||
# Definition for a Longsword
|
||||
[Entry:Longsword]
|
||||
[Longsword]
|
||||
Name (text) = Longsword
|
||||
Description (text-field) = A standard sword with a long blade and crossguard.
|
||||
Category (text) = Weapon
|
||||
|
||||
[Entry.Longsword.Stats]
|
||||
[Longsword.Stats]
|
||||
Damage (text) = 1d8 slashing
|
||||
Weight (number) = 3
|
||||
Cost (number) = 15
|
||||
@ -205,25 +205,25 @@ Guid = a1b2c3d4-e5f6-7890-abcd-ef1234567890
|
||||
Version = 1.0.0
|
||||
TemplateRef = Standard Fantasy Character|f9e8d7c6-b5a4-3210-9876-543210fedcba|2.1.0
|
||||
|
||||
[Section: Info]
|
||||
[Info]
|
||||
CharacterName = Elara
|
||||
# The value 'Ranger' is an entry from the 'ClassData' dataset,
|
||||
# as specified by the 'Class' field in the template.
|
||||
Class = Ranger
|
||||
|
||||
[Section: AbilityScores]
|
||||
[AbilityScores]
|
||||
Strength = 16
|
||||
Dexterity = 14
|
||||
|
||||
# Calculated values are not stored in the character file.
|
||||
|
||||
[Section: Equipment]
|
||||
[Equipment]
|
||||
# 'PrimaryWeapon' is a user-defined field name for the item.
|
||||
# The value 'ItemData.Longsword' refers to the 'Longsword' entry in the 'ItemData' dataset.
|
||||
PrimaryWeapon = ItemData.Longsword
|
||||
PrimaryWeapon.Quantity = 1
|
||||
|
||||
[Section: Inventory]
|
||||
[Inventory]
|
||||
HealingPotion = ItemData.HealingPotion
|
||||
HealingPotion.Quantity = 5
|
||||
HempRope = ItemData.HempRope
|
||||
|
Loading…
Reference in New Issue
Block a user