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