| File: Documentation\ApiDocs\ApiSitemapParser.cs | Web Access |
| Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Xml; namespace Aspire.Cli.Documentation.ApiDocs; /// <summary> /// Represents a single API reference route discovered from the site map. /// </summary> internal sealed class ApiSitemapEntry { /// <summary> /// Gets the canonical page URL. /// </summary> public required string Url { get; init; } /// <summary> /// Gets the API language for the route. /// </summary> public required string Language { get; init; } /// <summary> /// Gets the path segments that follow the language portion of the route. /// </summary> public required string[] Segments { get; init; } } /// <summary> /// Parses Aspire API reference routes from the public sitemap. /// </summary> internal static class ApiSitemapParser { private static readonly ApiLanguageRoute[] s_supportedLanguageRoutes = [ new(ApiReferenceLanguages.CSharp, "/reference/api/csharp"), new(ApiReferenceLanguages.TypeScript, "/reference/api/typescript") ]; /// <summary> /// Parses supported Aspire API routes from sitemap content. /// </summary> /// <param name="sitemapContent">The raw sitemap XML.</param> /// <returns>The parsed API routes.</returns> public static IReadOnlyList<ApiSitemapEntry> Parse(string sitemapContent) { if (string.IsNullOrWhiteSpace(sitemapContent)) { return []; } using var stringReader = new StringReader(sitemapContent); using var xmlReader = XmlReader.Create(stringReader, new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, IgnoreComments = true, IgnoreWhitespace = true }); var entries = new List<ApiSitemapEntry>(); while (xmlReader.Read()) { if (xmlReader.NodeType is not XmlNodeType.Element || xmlReader.LocalName != "loc") { continue; } var location = xmlReader.ReadElementContentAsString(); if (!TryParseApiRoute(location, out var entry)) { continue; } entries.Add(entry); } return entries; } private static bool TryParseApiRoute(string location, out ApiSitemapEntry entry) { entry = default!; if (!Uri.TryCreate(location, UriKind.Absolute, out var uri)) { return false; } var absolutePath = uri.AbsolutePath.AsSpan(); foreach (var route in s_supportedLanguageRoutes) { if (!absolutePath.StartsWith(route.PathPrefix, StringComparison.OrdinalIgnoreCase)) { continue; } var remainder = absolutePath[route.PathPrefix.Length..]; if (remainder.IsEmpty) { return false; } if (remainder[0] != '/') { continue; } var routeSegments = remainder[1..] .Trim('/') .ToString() .Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (routeSegments.Length is 0) { return false; } entry = new ApiSitemapEntry { Url = uri.GetLeftPart(UriPartial.Path).TrimEnd('/'), Language = route.Language, Segments = routeSegments }; return true; } return false; } private readonly record struct ApiLanguageRoute(string Language, string PathPrefix); }