From 589495911dde3ba8d444bed13775b21d2606d423 Mon Sep 17 00:00:00 2001 From: Frederik Jacobsen Date: Sat, 18 Oct 2025 23:05:08 +0200 Subject: [PATCH] Added simple export functionality --- .../Views/LeftTreeView.axaml | 1 + .../Views/LeftTreeView.axaml.cs | 131 ++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/XSDVisualiser.Desktop/Views/LeftTreeView.axaml b/XSDVisualiser.Desktop/Views/LeftTreeView.axaml index e5022ee..95ba62d 100644 --- a/XSDVisualiser.Desktop/Views/LeftTreeView.axaml +++ b/XSDVisualiser.Desktop/Views/LeftTreeView.axaml @@ -39,6 +39,7 @@ + diff --git a/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs b/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs index 6a6382f..738e51b 100644 --- a/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs +++ b/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs @@ -1,6 +1,9 @@ using System.Globalization; using System.Text; using System.Threading.Tasks; +using System.Xml; +using System.IO; +using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; @@ -251,4 +254,132 @@ public partial class LeftTreeView : UserControl return string.Join(";", parts); } + + private async void OnExportXmlClick(object? sender, RoutedEventArgs e) + { + // Determine the node from the menu item's DataContext + var node = (sender as MenuItem)?.DataContext as SchemaNode ?? (sender as Control)?.GetVisualAncestors().OfType().FirstOrDefault()?.DataContext as SchemaNode; + if (node == null) + return; + + var top = TopLevel.GetTopLevel(this); + var sp = top?.StorageProvider; + if (sp == null) + return; + + var suggestedName = string.IsNullOrWhiteSpace(node.Name) ? "schema-node.xml" : $"{node.Name}.xml"; + var saveOptions = new FilePickerSaveOptions + { + Title = "Export subtree as XML", + SuggestedFileName = suggestedName, + DefaultExtension = "xml", + ShowOverwritePrompt = true, + FileTypeChoices = new List + { + new FilePickerFileType("XML file") { Patterns = new List { "*.xml" }, MimeTypes = new List{"application/xml","text/xml"} }, + FilePickerFileTypes.All + } + }; + + var dest = await sp.SaveFilePickerAsync(saveOptions); + if (dest == null) return; + + var localPath = dest.TryGetLocalPath(); + if (string.IsNullOrEmpty(localPath)) + { + await ShowTextDialogAsync("Export XML", "The selected destination is not accessible as a local path."); + return; + } + + try + { + var xml = BuildXmlString(node); + await File.WriteAllTextAsync(localPath!, xml, Encoding.UTF8); + await ShowTextDialogAsync("Export XML", $"Exported subtree to:\n{localPath}"); + } + catch (Exception ex) + { + await ShowTextDialogAsync("Export XML", $"Failed to export XML.\n\n{ex.Message}"); + } + } + + private static string BuildXmlString(SchemaNode root) + { + var settings = new XmlWriterSettings + { + Indent = true, + OmitXmlDeclaration = false, + Encoding = Encoding.UTF8, + NewLineOnAttributes = false + }; + + var sb = new StringBuilder(); + using (var writer = XmlWriter.Create(sb, settings)) + { + writer.WriteStartDocument(); + WriteElementRecursive(writer, root); + writer.WriteEndDocument(); + } + return sb.ToString(); + } + + private static void WriteElementRecursive(XmlWriter writer, SchemaNode node) + { + var localName = string.IsNullOrWhiteSpace(node.Name) ? "Element" : node.Name!; + if (!string.IsNullOrEmpty(node.Namespace)) + writer.WriteStartElement(localName, node.Namespace); + else + writer.WriteStartElement(localName); + + // Write attributes if any (use type + constraints as placeholder value) + foreach (var attr in node.Attributes) + { + var attrName = string.IsNullOrWhiteSpace(attr.Name) ? "attr" : attr.Name!; + var value = BuildTypeAndConstraintText(attr.BuiltInType ?? attr.TypeName, attr.Constraints); + if (!string.IsNullOrEmpty(attr.Namespace)) + writer.WriteAttributeString(attrName, attr.Namespace, value); + else + writer.WriteAttributeString(attrName, value); + } + + var hasChildren = node.Children is { Count: > 0 }; + if (hasChildren) + { + foreach (var child in node.Children) + { + WriteElementRecursive(writer, child); + } + } + else + { + // leaf node: place datatype and constraints in text content + var text = BuildTypeAndConstraintText(node.BuiltInType ?? node.TypeName, node.Constraints); + if (!string.IsNullOrWhiteSpace(text)) + writer.WriteString(text); + } + + writer.WriteEndElement(); + } + + private static string BuildTypeAndConstraintText(string? type, ConstraintSet? constraintSet) + { + var cons = constraintSet?.Constraints; + var typePart = string.IsNullOrWhiteSpace(type) ? string.Empty : type.Trim(); + + var constraintsPart = string.Empty; + if (cons is { Count: > 0 }) + { + var tmpNode = new SchemaNode { Constraints = constraintSet }; + constraintsPart = FormatConstraints(tmpNode); + } + + if (string.IsNullOrEmpty(typePart) && string.IsNullOrEmpty(constraintsPart)) + return string.Empty; + + if (string.IsNullOrEmpty(constraintsPart)) + return typePart; + return string.IsNullOrEmpty(typePart) ? constraintsPart : + $"{typePart} {constraintsPart}"; + } + } \ No newline at end of file