diff --git a/XSDVisualiser.Desktop/Views/LeftTreeView.axaml b/XSDVisualiser.Desktop/Views/LeftTreeView.axaml
index df5dd63..95f5a6b 100644
--- a/XSDVisualiser.Desktop/Views/LeftTreeView.axaml
+++ b/XSDVisualiser.Desktop/Views/LeftTreeView.axaml
@@ -30,6 +30,11 @@
BorderBrush="{Binding Cardinality, Converter={StaticResource OptionalToBrush}}"
BorderThickness="{Binding Cardinality, Converter={StaticResource OptionalToThickness}}"
Padding="8" Margin="0,0,8,6" Background="{DynamicResource PanelBackgroundBrush}">
+
+
+
+
+
diff --git a/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs b/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs
index 4541ea3..06e4a03 100644
--- a/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs
+++ b/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs
@@ -1,4 +1,10 @@
+using System.Globalization;
+using System.Text;
+using Avalonia;
using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+using XSDVisualiser.Models;
namespace XSDVisualiser.Desktop.Views
{
@@ -8,5 +14,70 @@ namespace XSDVisualiser.Desktop.Views
{
InitializeComponent();
}
+
+ 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 = (sender as Control)?.GetVisualAncestors().OfType().FirstOrDefault()?.DataContext as SchemaNode;
+ if (node == null)
+ return;
+
+ var tsv = BuildTsv(node);
+
+ var topLevel = TopLevel.GetTopLevel(this);
+ if (topLevel?.Clipboard != null)
+ {
+ await topLevel.Clipboard.SetTextAsync(tsv);
+ }
+ }
+
+ private static string BuildTsv(SchemaNode root)
+ {
+ var sb = new StringBuilder();
+ // Header
+ sb.AppendLine("Depth\tName\tNamespace\tTypeName\tBuiltInType\tMinOccurs\tMaxOccurs\tContentModel\tIsNillable");
+ AppendNode(sb, root, 0);
+ return sb.ToString();
+ }
+
+ private static void AppendNode(StringBuilder sb, SchemaNode node, int depth)
+ {
+ string San(string? s)
+ {
+ if (string.IsNullOrEmpty(s)) return string.Empty;
+ return s.Replace("\t", " ").Replace("\r", " ").Replace("\n", " ");
+ }
+
+ var min = node.Cardinality?.Min.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
+ string max;
+ if (node.Cardinality?.MaxIsUnbounded == true)
+ max = "unbounded";
+ else
+ max = node.Cardinality != null ? node.Cardinality.Max.ToString(CultureInfo.InvariantCulture) : string.Empty;
+
+ var line = string.Join("\t", new[]
+ {
+ depth.ToString(CultureInfo.InvariantCulture),
+ San(node.Name),
+ San(node.Namespace),
+ San(node.TypeName),
+ San(node.BuiltInType),
+ min,
+ max,
+ San(node.ContentModel),
+ node.IsNillable ? "true" : "false"
+ });
+ sb.AppendLine(line);
+
+ if (node.Children != null)
+ {
+ foreach (var child in node.Children)
+ {
+ AppendNode(sb, child, depth + 1);
+ }
+ }
+ }
}
}