Started working on documentation

This commit is contained in:
Frederik Jacobsen 2025-10-19 00:05:28 +02:00
parent e8c30e8a05
commit aacaaee493
7 changed files with 101 additions and 19 deletions

View File

@ -4,6 +4,10 @@ using XSDVisualiser.Core;
namespace XSDVisualiser.Desktop.ViewModels;
/// <summary>
/// View model exposing the parsed schema for binding in the main window.
/// Provides selection state and information about the currently loaded XSD file.
/// </summary>
public class MainWindowViewModel(SchemaModel model) : INotifyPropertyChanged
{
private string? _currentFilePath;
@ -11,6 +15,10 @@ public class MainWindowViewModel(SchemaModel model) : INotifyPropertyChanged
private SchemaNode? _selectedNode;
private SchemaNode? _selectedRootElement;
/// <summary>
/// Parsed schema model currently displayed by the UI.
/// Setting this property updates dependent properties and notifies bindings.
/// </summary>
public SchemaModel Model
{
get => _model;
@ -24,8 +32,14 @@ public class MainWindowViewModel(SchemaModel model) : INotifyPropertyChanged
}
}
/// <summary>
/// All global elements defined in the loaded schema.
/// </summary>
public IEnumerable<SchemaNode> RootElements => _model?.RootElements ?? Enumerable.Empty<SchemaNode>();
/// <summary>
/// If set, filters the view to show only this root element and its subtree.
/// </summary>
public SchemaNode? SelectedRootElement
{
get => _selectedRootElement;

View File

@ -15,6 +15,10 @@ using XSDVisualiser.Desktop.ViewModels;
namespace XSDVisualiser.Desktop.Views;
/// <summary>
/// Left-side tree view control that displays parsed XSD schema nodes and provides actions
/// such as copy-as-TSV, validate against XML, and export sample XML.
/// </summary>
public partial class LeftTreeView : UserControl
{
public LeftTreeView()
@ -24,7 +28,6 @@ public partial class LeftTreeView : UserControl
private async void OnCopyAsTsvClick(object? sender, RoutedEventArgs e)
{
// Determine the node from the menu item's DataContext
var node = (sender as MenuItem)?.DataContext as SchemaNode;
if (node == null)
node =
@ -40,7 +43,6 @@ public partial class LeftTreeView : UserControl
private async void OnValidateAgainstXmlClick(object? sender, RoutedEventArgs e)
{
// Resolve node from context
var node = (sender as MenuItem)?.DataContext as SchemaNode;
if (node == null)
node = (sender as Control)?.GetVisualAncestors().OfType<Control>().FirstOrDefault()?.DataContext as SchemaNode;
@ -96,7 +98,6 @@ public partial class LeftTreeView : UserControl
private static string BuildTsv(SchemaNode root)
{
var sb = new StringBuilder();
// Header
sb.AppendLine(
"Depth\tPath\tName\tNamespace\tTypeName\tBuiltInType\tMinOccurs\tMaxOccurs\tContentModel\tConstraints\tIsNillable");
var initialPath = root.Name ?? string.Empty;
@ -146,7 +147,6 @@ public partial class LeftTreeView : UserControl
private static async Task<string?> TryGetLocalPathAsync(IStorageFile file)
{
// Avalonia provides TryGetLocalPath extension for local files; otherwise, we can't access it via System.IO API.
return file.TryGetLocalPath();
}
@ -183,10 +183,8 @@ public partial class LeftTreeView : UserControl
}
};
// Compose content
if (dialog.Content is Grid grid)
{
// Use a read-only TextBox so users can select text easily
var textBox = new TextBox
{
Text = text,
@ -198,7 +196,6 @@ public partial class LeftTreeView : UserControl
var scroller = new ScrollViewer { Content = textBox };
// Copy button
var copyButton = new Button
{
Content = "Copy",
@ -212,7 +209,6 @@ public partial class LeftTreeView : UserControl
if (topLevel?.Clipboard != null)
{
await topLevel.Clipboard.SetTextAsync(text);
// Provide lightweight feedback by changing content briefly
if (copyButton.Content is string)
copyButton.Content = "Copied";
await Task.Delay(1000);

View File

@ -7,15 +7,33 @@ namespace XSDVisualiser.Core;
/// </summary>
public class AttributeInfo
{
/// <summary>
/// Local name of the attribute.
/// </summary>
[XmlAttribute] public string? Name { get; set; }
/// <summary>
/// Namespace URI of the attribute, if any.
/// </summary>
[XmlAttribute] public string? Namespace { get; set; }
[XmlAttribute] public string? Use { get; set; } // optional | required | prohibited
/// <summary>
/// Usage of the attribute: optional | required | prohibited.
/// </summary>
[XmlAttribute] public string? Use { get; set; }
/// <summary>
/// The qualified type name if the attribute has a named type.
/// </summary>
[XmlAttribute] public string? TypeName { get; set; }
/// <summary>
/// Built-in XML Schema type code name when applicable.
/// </summary>
[XmlAttribute] public string? BuiltInType { get; set; }
/// <summary>
/// Optional constraints applied to the attribute's simple type.
/// </summary>
[XmlElement] public ConstraintSet? Constraints { get; set; }
}

View File

@ -7,9 +7,14 @@ namespace XSDVisualiser.Core;
/// </summary>
public class ConstraintSet
{
/// <summary>
/// The qualified name of the base type this constraint set applies to, if any.
/// </summary>
[XmlAttribute] public string? BaseTypeName { get; set; }
// Generic catch-all list of constraints for dynamic display and tooling
/// <summary>
/// A catch-all list of constraint entries for dynamic display and tooling.
/// </summary>
[XmlArray("Constraints")]
[XmlArrayItem("Constraint")]
public List<ConstraintEntry> Constraints { get; set; } = new();

View File

@ -29,7 +29,10 @@ public class SchemaNode
[XmlArrayItem("Element")]
public List<SchemaNode> Children { get; set; } = new();
[XmlAttribute] public string? ContentModel { get; set; } // sequence | choice | all | simple
/// <summary>
/// Content model for this node: sequence | choice | all | simple. For synthetic group nodes, may be "group".
/// </summary>
[XmlAttribute] public string? ContentModel { get; set; }
/// <summary>
/// Human-readable documentation extracted from xsd:annotation/xsd:documentation.
@ -38,14 +41,13 @@ public class SchemaNode
[XmlElement]
public string? Documentation { get; set; }
/// <summary>
/// Returns a human-readable name for UI elements (prefers Name, then TypeName).
/// </summary>
public override string ToString()
{
// Used by AutoCompleteBox to get text for filtering and matching
// Prefer Name, then TypeName, and fall back to base implementation
if (!string.IsNullOrEmpty(Name))
return Name!;
if (!string.IsNullOrEmpty(TypeName))
return TypeName!;
if (!string.IsNullOrEmpty(Name)) return Name!;
if (!string.IsNullOrEmpty(TypeName)) return TypeName!;
return base.ToString();
}
}

View File

@ -4,10 +4,18 @@ using System.Xml.Schema;
namespace XSDVisualiser.Core;
/// <summary>
/// Parses XML Schema (XSD) into a simplified model for visualization and tooling.
/// </summary>
public class XsdSchemaParser
{
private readonly XmlSchemaSet _set = new();
/// <summary>
/// Reads and compiles the provided XSD file into a schema set, then produces a simplified schema model.
/// </summary>
/// <param name="xsdPath">Path to the XSD file.</param>
/// <returns>A populated <see cref="SchemaModel"/> representing global elements and their structures.</returns>
public SchemaModel Parse(string xsdPath)
{
_set.XmlResolver = new XmlUrlResolver();
@ -34,8 +42,7 @@ public class XsdSchemaParser
private void ValidationCallback(object? sender, ValidationEventArgs e)
{
// For now, we do not throw; we capture compiled info best-effort.
// Console.Error.WriteLine($"[XSD Validation {e.Severity}] {e.Message}");
// Intentionally no-op: schema is parsed best-effort without throwing on compile warnings/errors.
}
private SchemaNode BuildNodeForElement(XmlSchemaElement element, string? parentContentModel)

View File

@ -11,12 +11,29 @@ namespace XSDVisualiser.Core;
/// </summary>
public static class XmlValidator
{
/// <summary>
/// Validates an XML document against the global element specified by name and optional namespace
/// from the XSD located at <paramref name="xsdPath"/>.
/// </summary>
/// <param name="xsdPath">Path to the XSD file containing the target element/type definitions.</param>
/// <param name="elementName">The local name of the global element to validate against.</param>
/// <param name="elementNamespace">The namespace URI of the element; may be null to auto-detect.</param>
/// <param name="xmlPath">Path to the XML file to validate.</param>
/// <returns>Aggregated validation result with errors/warnings and diagnostics.</returns>
public static XmlValidationResult ValidateAgainstElement(string xsdPath, string elementName, string? elementNamespace, string xmlPath)
{
var set = BuildSchemaSet(xsdPath);
return ValidateAgainstElement(set, elementName, elementNamespace, xmlPath);
}
/// <summary>
/// Validates an XML document against a specific global element within an already built <see cref="XmlSchemaSet"/>.
/// </summary>
/// <param name="schemas">Compiled schema set to use for validation.</param>
/// <param name="elementName">The local name of the global element to validate against.</param>
/// <param name="elementNamespace">The namespace URI of the element; may be null to auto-detect.</param>
/// <param name="xmlPath">Path to the XML file to validate.</param>
/// <returns>Aggregated validation result with errors/warnings and diagnostics.</returns>
public static XmlValidationResult ValidateAgainstElement(XmlSchemaSet schemas, string elementName, string? elementNamespace, string xmlPath)
{
var result = new XmlValidationResult();
@ -428,15 +445,31 @@ public static class XmlValidator
}
}
/// <summary>
/// Aggregates XML validation outcomes, including errors, warnings, and overall validity.
/// </summary>
public sealed class XmlValidationResult
{
private readonly List<XmlValidationIssue> _issues = new();
/// <summary>
/// True if no validation errors have been recorded.
/// </summary>
public bool IsValid => _issues.TrueForAll(i => i.Severity != XmlSeverityType.Error);
/// <summary>
/// All recorded validation issues (errors and warnings) in chronological order.
/// </summary>
public IReadOnlyList<XmlValidationIssue> Issues => _issues;
/// <summary>
/// All recorded validation errors.
/// </summary>
public IEnumerable<XmlValidationIssue> Errors => _issues.Where(i => i.Severity == XmlSeverityType.Error);
/// <summary>
/// All recorded validation warnings.
/// </summary>
public IEnumerable<XmlValidationIssue> Warnings => _issues.Where(i => i.Severity == XmlSeverityType.Warning);
internal void AddError(string message, int? line = null, int? position = null) =>
@ -446,6 +479,13 @@ public sealed class XmlValidationResult
_issues.Add(new XmlValidationIssue(XmlSeverityType.Warning, message, line ?? 0, position ?? 0));
}
/// <summary>
/// Represents a single validation issue (error or warning) with optional location information.
/// </summary>
/// <param name="Severity">Issue severity (Error or Warning).</param>
/// <param name="Message">Human-readable description of the issue.</param>
/// <param name="LineNumber">Line number in the XML where the issue occurred, if available.</param>
/// <param name="LinePosition">Column position in the XML where the issue occurred, if available.</param>
public sealed record XmlValidationIssue(XmlSeverityType Severity, string Message, int LineNumber, int LinePosition);