Uses XPath
dotnet run parse.cs "<url>" "<xpath-query>"
dotnet run parse.cs "https://httpstatus.io/sitemap.xml" "//ns:url" | #:package Spectre.Console@0.* | |
| #:package Spectre.Console.Cli@0.* | |
| #:property PublishAot=false | |
| using System.ComponentModel; | |
| using System.Diagnostics.CodeAnalysis; | |
| using System.Xml; | |
| using System.Xml.XPath; | |
| using Spectre.Console; | |
| using Spectre.Console.Cli; | |
| CommandApp<ParseXmlCommand> app = new(); | |
| return await app.RunAsync(args); | |
| internal sealed class ParseXmlCommand : AsyncCommand<ParseXmlCommand.Settings> | |
| { | |
| public sealed class Settings : CommandSettings | |
| { | |
| [CommandArgument(0, "<URL>")] | |
| [Description("The URL of the XML document to parse")] | |
| public string Url { get; init; } = string.Empty; | |
| [CommandArgument(1, "<XPATH>")] | |
| [Description("The XPath query to execute against the XML document")] | |
| public string XPath { get; init; } = string.Empty; | |
| } | |
| public override async Task<int> ExecuteAsync([NotNull] CommandContext context, [NotNull] Settings settings, CancellationToken cancellationToken) | |
| { | |
| try | |
| { | |
| await AnsiConsole.Status() | |
| .Spinner(Spinner.Known.Dots) | |
| .StartAsync("Fetching XML...", async ctx => | |
| { | |
| using HttpClient httpClient = new(); | |
| string xmlContent = await httpClient.GetStringAsync(settings.Url, cancellationToken); | |
| ctx.Status("Parsing XML..."); | |
| XmlDocument xmlDoc = new(); | |
| xmlDoc.LoadXml(xmlContent); | |
| ctx.Status("Executing XPath query..."); | |
| XPathNavigator? navigator = xmlDoc.CreateNavigator(); | |
| if (navigator == null) | |
| { | |
| AnsiConsole.MarkupLine("[red]Failed to create XML navigator[/]"); | |
| return; | |
| } | |
| // Create namespace manager and auto-detect namespaces | |
| XmlNamespaceManager namespaceManager = new(navigator.NameTable); | |
| IDictionary<string, string> namespaces = navigator.GetNamespacesInScope(XmlNamespaceScope.All); | |
| if (namespaces != null) | |
| { | |
| foreach (var ns in namespaces) | |
| { | |
| if (!string.IsNullOrEmpty(ns.Key)) | |
| { | |
| namespaceManager.AddNamespace(ns.Key, ns.Value); | |
| } | |
| } | |
| } | |
| // If there's a default namespace, add it with a prefix | |
| var defaultNamespace = xmlDoc.DocumentElement?.NamespaceURI; | |
| if (!string.IsNullOrEmpty(defaultNamespace) && namespaces != null && !namespaces.Values.Contains(defaultNamespace)) | |
| { | |
| namespaceManager.AddNamespace("ns", defaultNamespace); | |
| AnsiConsole.MarkupLine($"[dim]Tip: Default namespace detected. Use 'ns:' prefix in XPath (e.g., //ns:url)[/]"); | |
| } | |
| XPathNodeIterator? nodes = navigator.Select(settings.XPath, namespaceManager); | |
| var table = new Table(); | |
| table.Border(TableBorder.Rounded); | |
| table.AddColumn(new TableColumn("[bold yellow]Property[/]")); | |
| table.AddColumn(new TableColumn("[bold cyan]Value[/]")); | |
| table.AddRow("[grey]URL[/]", $"[link]{settings.Url}[/]"); | |
| table.AddRow("[grey]XPath[/]", $"[green]{settings.XPath}[/]"); | |
| table.AddRow("[grey]Nodes Found[/]", $"[bold blue]{nodes?.Count ?? 0}[/]"); | |
| AnsiConsole.WriteLine(); | |
| if (nodes != null && nodes.Count > 0) | |
| { | |
| AnsiConsole.WriteLine(); | |
| AnsiConsole.MarkupLine("[bold]Results:[/]"); | |
| var panel = new Panel(string.Join(Environment.NewLine, | |
| nodes.Cast<XPathNavigator>().Select(n => n.Value))) | |
| { | |
| Border = BoxBorder.Rounded, | |
| BorderStyle = new Style(Color.Green) | |
| }; | |
| AnsiConsole.Write(panel); | |
| } | |
| AnsiConsole.WriteLine(); | |
| AnsiConsole.Write(table); | |
| }); | |
| return 0; | |
| } | |
| catch (HttpRequestException ex) | |
| { | |
| AnsiConsole.MarkupLine($"[red bold]Error fetching URL:[/] {ex.Message}"); | |
| return 1; | |
| } | |
| catch (XmlException ex) | |
| { | |
| AnsiConsole.MarkupLine($"[red bold]Error parsing XML:[/] {ex.Message}"); | |
| return 1; | |
| } | |
| catch (XPathException ex) | |
| { | |
| AnsiConsole.MarkupLine($"[red bold]Error with XPath query:[/] {ex.Message}"); | |
| return 1; | |
| } | |
| catch (Exception ex) | |
| { | |
| AnsiConsole.MarkupLine($"[red bold]Error:[/] {ex.Message}"); | |
| return 1; | |
| } | |
| } | |
| } |