< Summary

Information
Class: Chronicis.Api.Services.LinkParser
Assembly: Chronicis.Api
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Api/Services/LinkParser.cs
Line coverage
100%
Covered lines: 27
Uncovered lines: 0
Coverable lines: 27
Total lines: 96
Line coverage: 100%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ParseLinks(...)100%66100%
ParseHtmlLinks(...)100%44100%
ParseLegacyLinks(...)100%66100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Api/Services/LinkParser.cs

#LineLine coverage
 1using System.Text.RegularExpressions;
 2
 3namespace Chronicis.Api.Services;
 4
 5/// <summary>
 6/// Parses wiki-style links from article content using regex pattern matching.
 7/// Supports both legacy markdown format and modern HTML span format.
 8/// </summary>
 9public sealed partial class LinkParser : ILinkParser
 10{
 11
 12    /// <summary>
 13    /// Extracts all wiki links from the given article body.
 14    /// Supports both legacy [[guid|text]] format and HTML span format.
 15    /// </summary>
 16    /// <param name="body">The article body to parse.</param>
 17    /// <returns>Collection of parsed links with target ID, display text, and position.</returns>
 18    public IEnumerable<ParsedLink> ParseLinks(string? body)
 19    {
 20        // Return empty if body is null or empty
 3321        if (string.IsNullOrEmpty(body))
 22        {
 323            return Enumerable.Empty<ParsedLink>();
 24        }
 25
 3026        var links = new List<ParsedLink>();
 3027        var processedGuids = new HashSet<Guid>(); // Track unique links
 28
 29        // Parse HTML span format (TipTap output) - check for marker first
 3030        if (body.Contains("data-target-id=", StringComparison.Ordinal))
 31        {
 932            ParseHtmlLinks(body, links, processedGuids);
 33        }
 34
 35        // Parse legacy markdown format for backwards compatibility
 3036        if (body.Contains("[[", StringComparison.Ordinal))
 37        {
 2038            ParseLegacyLinks(body, links, processedGuids);
 39        }
 40
 3041        return links;
 42    }
 43
 44    private static void ParseHtmlLinks(string body, List<ParsedLink> links, HashSet<Guid> processedGuids)
 45    {
 946        var matches = HtmlLinkRegex().Matches(body);
 47
 4048        foreach (Match match in matches)
 49        {
 1150            var targetArticleId = Guid.Parse(match.Groups[1].Value);
 51
 52            // Skip if we've already processed this target
 1153            if (!processedGuids.Add(targetArticleId))
 54            {
 55                continue;
 56            }
 57
 58            // Get display text and trim whitespace
 1059            var displayText = match.Groups[2].Value.Trim();
 60
 1061            var position = match.Index;
 62
 1063            links.Add(new ParsedLink(targetArticleId, displayText, position));
 64        }
 965    }
 66
 67    [GeneratedRegex(@"<span[^>]+data-target-id=""([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F
 68    private static partial Regex HtmlLinkRegex();
 69
 70    [GeneratedRegex(@"\[\[([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})(?:\|([^\]]+))?\]
 71    private static partial Regex LegacyLinkRegex();
 72
 73    private static void ParseLegacyLinks(string body, List<ParsedLink> links, HashSet<Guid> processedGuids)
 74    {
 2075        var matches = LegacyLinkRegex().Matches(body);
 76
 8677        foreach (Match match in matches)
 78        {
 2379            var targetArticleId = Guid.Parse(match.Groups[1].Value);
 80
 81            // Skip if we've already processed this target (from HTML parsing)
 2382            if (!processedGuids.Add(targetArticleId))
 83            {
 84                continue;
 85            }
 86
 2187            var displayText = match.Groups[2].Success
 2188                ? match.Groups[2].Value.Trim()
 2189                : null;
 90
 2191            var position = match.Index;
 92
 2193            links.Add(new ParsedLink(targetArticleId, displayText, position));
 94        }
 2095    }
 96}