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; 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 public class MainWindowViewModel(SchemaModel model) : INotifyPropertyChanged
{ {
private string? _currentFilePath; private string? _currentFilePath;
@ -11,6 +15,10 @@ public class MainWindowViewModel(SchemaModel model) : INotifyPropertyChanged
private SchemaNode? _selectedNode; private SchemaNode? _selectedNode;
private SchemaNode? _selectedRootElement; 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 public SchemaModel Model
{ {
get => _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>(); 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 public SchemaNode? SelectedRootElement
{ {
get => _selectedRootElement; get => _selectedRootElement;

View File

@ -15,6 +15,10 @@ using XSDVisualiser.Desktop.ViewModels;
namespace XSDVisualiser.Desktop.Views; 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 partial class LeftTreeView : UserControl
{ {
public LeftTreeView() public LeftTreeView()
@ -24,7 +28,6 @@ public partial class LeftTreeView : UserControl
private async void OnCopyAsTsvClick(object? sender, RoutedEventArgs e) 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; var node = (sender as MenuItem)?.DataContext as SchemaNode;
if (node == null) if (node == null)
node = node =
@ -40,7 +43,6 @@ public partial class LeftTreeView : UserControl
private async void OnValidateAgainstXmlClick(object? sender, RoutedEventArgs e) private async void OnValidateAgainstXmlClick(object? sender, RoutedEventArgs e)
{ {
// Resolve node from context
var node = (sender as MenuItem)?.DataContext as SchemaNode; var node = (sender as MenuItem)?.DataContext as SchemaNode;
if (node == null) if (node == null)
node = (sender as Control)?.GetVisualAncestors().OfType<Control>().FirstOrDefault()?.DataContext as SchemaNode; 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) private static string BuildTsv(SchemaNode root)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
// Header
sb.AppendLine( sb.AppendLine(
"Depth\tPath\tName\tNamespace\tTypeName\tBuiltInType\tMinOccurs\tMaxOccurs\tContentModel\tConstraints\tIsNillable"); "Depth\tPath\tName\tNamespace\tTypeName\tBuiltInType\tMinOccurs\tMaxOccurs\tContentModel\tConstraints\tIsNillable");
var initialPath = root.Name ?? string.Empty; var initialPath = root.Name ?? string.Empty;
@ -146,7 +147,6 @@ public partial class LeftTreeView : UserControl
private static async Task<string?> TryGetLocalPathAsync(IStorageFile file) 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(); return file.TryGetLocalPath();
} }
@ -183,10 +183,8 @@ public partial class LeftTreeView : UserControl
} }
}; };
// Compose content
if (dialog.Content is Grid grid) if (dialog.Content is Grid grid)
{ {
// Use a read-only TextBox so users can select text easily
var textBox = new TextBox var textBox = new TextBox
{ {
Text = text, Text = text,
@ -198,7 +196,6 @@ public partial class LeftTreeView : UserControl
var scroller = new ScrollViewer { Content = textBox }; var scroller = new ScrollViewer { Content = textBox };
// Copy button
var copyButton = new Button var copyButton = new Button
{ {
Content = "Copy", Content = "Copy",
@ -212,7 +209,6 @@ public partial class LeftTreeView : UserControl
if (topLevel?.Clipboard != null) if (topLevel?.Clipboard != null)
{ {
await topLevel.Clipboard.SetTextAsync(text); await topLevel.Clipboard.SetTextAsync(text);
// Provide lightweight feedback by changing content briefly
if (copyButton.Content is string) if (copyButton.Content is string)
copyButton.Content = "Copied"; copyButton.Content = "Copied";
await Task.Delay(1000); await Task.Delay(1000);

View File

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

View File

@ -7,9 +7,14 @@ namespace XSDVisualiser.Core;
/// </summary> /// </summary>
public class ConstraintSet 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; } [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")] [XmlArray("Constraints")]
[XmlArrayItem("Constraint")] [XmlArrayItem("Constraint")]
public List<ConstraintEntry> Constraints { get; set; } = new(); public List<ConstraintEntry> Constraints { get; set; } = new();

View File

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

View File

@ -4,10 +4,18 @@ using System.Xml.Schema;
namespace XSDVisualiser.Core; namespace XSDVisualiser.Core;
/// <summary>
/// Parses XML Schema (XSD) into a simplified model for visualization and tooling.
/// </summary>
public class XsdSchemaParser public class XsdSchemaParser
{ {
private readonly XmlSchemaSet _set = new(); 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) public SchemaModel Parse(string xsdPath)
{ {
_set.XmlResolver = new XmlUrlResolver(); _set.XmlResolver = new XmlUrlResolver();
@ -34,8 +42,7 @@ public class XsdSchemaParser
private void ValidationCallback(object? sender, ValidationEventArgs e) private void ValidationCallback(object? sender, ValidationEventArgs e)
{ {
// For now, we do not throw; we capture compiled info best-effort. // Intentionally no-op: schema is parsed best-effort without throwing on compile warnings/errors.
// Console.Error.WriteLine($"[XSD Validation {e.Severity}] {e.Message}");
} }
private SchemaNode BuildNodeForElement(XmlSchemaElement element, string? parentContentModel) private SchemaNode BuildNodeForElement(XmlSchemaElement element, string? parentContentModel)

View File

@ -11,12 +11,29 @@ namespace XSDVisualiser.Core;
/// </summary> /// </summary>
public static class XmlValidator 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) public static XmlValidationResult ValidateAgainstElement(string xsdPath, string elementName, string? elementNamespace, string xmlPath)
{ {
var set = BuildSchemaSet(xsdPath); var set = BuildSchemaSet(xsdPath);
return ValidateAgainstElement(set, elementName, elementNamespace, xmlPath); 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) public static XmlValidationResult ValidateAgainstElement(XmlSchemaSet schemas, string elementName, string? elementNamespace, string xmlPath)
{ {
var result = new XmlValidationResult(); 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 public sealed class XmlValidationResult
{ {
private readonly List<XmlValidationIssue> _issues = new(); 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); 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; public IReadOnlyList<XmlValidationIssue> Issues => _issues;
/// <summary>
/// All recorded validation errors.
/// </summary>
public IEnumerable<XmlValidationIssue> Errors => _issues.Where(i => i.Severity == XmlSeverityType.Error); 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); public IEnumerable<XmlValidationIssue> Warnings => _issues.Where(i => i.Severity == XmlSeverityType.Warning);
internal void AddError(string message, int? line = null, int? position = null) => 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)); _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); public sealed record XmlValidationIssue(XmlSeverityType Severity, string Message, int LineNumber, int LinePosition);