diff --git a/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs b/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs index 738e51b..78a65ee 100644 --- a/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs +++ b/XSDVisualiser.Desktop/Views/LeftTreeView.axaml.cs @@ -186,13 +186,40 @@ public partial class LeftTreeView : UserControl // Compose content if (dialog.Content is Grid grid) { - var textBlock = new TextBlock + // Use a read-only TextBox so users can select text easily + var textBox = new TextBox { Text = text, + IsReadOnly = true, + AcceptsReturn = true, TextWrapping = Avalonia.Media.TextWrapping.Wrap, Margin = new Thickness(12) }; - var scroller = new ScrollViewer { Content = textBlock }; + + var scroller = new ScrollViewer { Content = textBox }; + + // Copy button + var copyButton = new Button + { + Content = "Copy", + MinWidth = 80, + HorizontalAlignment = HorizontalAlignment.Right, + Margin = new Thickness(8) + }; + copyButton.Click += async (_, _) => + { + var topLevel = TopLevel.GetTopLevel(this); + 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); + if (copyButton.Content is string) + copyButton.Content = "Copy"; + } + }; var closeButton = new Button { @@ -208,6 +235,7 @@ public partial class LeftTreeView : UserControl Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right }; + buttonsPanel.Children.Add(copyButton); buttonsPanel.Children.Add(closeButton); Grid.SetRow(buttonsPanel, 1); diff --git a/XSDVisualiser/Utils/XmlValidation.cs b/XSDVisualiser/Utils/XmlValidation.cs index dfa8f28..4b09ee1 100644 --- a/XSDVisualiser/Utils/XmlValidation.cs +++ b/XSDVisualiser/Utils/XmlValidation.cs @@ -18,15 +18,7 @@ public static class XmlValidator { var result = new XmlValidationResult(); - // Ensure the requested element exists in the schema set - var qname = new XmlQualifiedName(elementName, elementNamespace ?? string.Empty); - if (schemas.GlobalElements[qname] is not XmlSchemaElement) - { - result.AddError($"Element '{qname}' was not found in the compiled schema set."); - return result; - } - - // Probe XML root element + // Probe XML root element first, we may use its namespace as a hint (string localName, string nsUri)? rootInfo = TryReadRoot(xmlPath); if (rootInfo is null) { @@ -35,6 +27,42 @@ public static class XmlValidator } var (rootLocal, rootNs) = rootInfo.Value; + + // Try to ensure the requested element exists in the schema set; if not, try to infer the correct namespace instead of failing hard. + var qname = new XmlQualifiedName(elementName, elementNamespace ?? string.Empty); + if (schemas.GlobalElements[qname] is not XmlSchemaElement) + { + // Try to find candidates with the same local name across namespaces + var candidates = schemas.GlobalElements.Names.Cast().Where(n => string.Equals(n.Name, elementName, StringComparison.Ordinal)).Distinct().ToList(); + if (candidates.Count == 1) + { + elementNamespace = candidates[0].Namespace; + qname = new XmlQualifiedName(elementName, elementNamespace ?? string.Empty); + result.AddWarning($"Element '{{{qname.Namespace}}}{qname.Name}' was not found with the provided namespace. Using detected namespace '{candidates[0].Namespace}'."); + } + else if (candidates.Count > 1) + { + // Prefer a candidate matching the XML root namespace if any + var preferred = candidates.FirstOrDefault(c => string.Equals(c.Namespace ?? string.Empty, rootNs ?? string.Empty, StringComparison.Ordinal)); + if (preferred != null) + { + elementNamespace = preferred.Namespace; + qname = new XmlQualifiedName(elementName, elementNamespace ?? string.Empty); + result.AddWarning($"Element namespace adjusted to match XML root namespace: '{{{preferred.Namespace}}}{preferred.Name}'."); + } + else + { + var list = string.Join(", ", candidates.Select(c => $"'{{{c.Namespace}}}{c.Name}'")); + result.AddWarning($"Element '{{{qname.Namespace}}}{qname.Name}' was not found in the compiled schema set. Candidates by name: {list}. Proceeding with best-effort validation."); + } + } + else + { + // No candidates at all; continue and let the validator report more actionable errors. + result.AddWarning($"Element '{{{qname.Namespace}}}{qname.Name}' was not found in the compiled schema set. Proceeding with best-effort validation."); + } + } + var matchesRoot = string.Equals(rootLocal, elementName, StringComparison.Ordinal) && string.Equals(rootNs ?? string.Empty, elementNamespace ?? string.Empty, StringComparison.Ordinal); var settings = new XmlReaderSettings @@ -99,8 +127,18 @@ public static class XmlValidator if (elementNode is null) { - result.AddError($"Could not find any element '{{{elementNamespace}}}{elementName}' in the XML document to validate against."); - return result; + // Try again ignoring namespace, in case the provided namespace was incorrect or omitted + var retry = FindFirstElementNode(xmlPath, elementName, null).Node; + if (retry is not null) + { + result.AddWarning($"Could not find element '{{{elementNamespace}}}{elementName}' with the specified namespace; validating first occurrence by local name only."); + elementNode = retry; + } + else + { + result.AddError($"Could not find any element '{{{elementNamespace}}}{elementName}' in the XML document to validate against."); + return result; + } } // Inform as a warning that we validate a subtree instead of the document root