Forge
csharp4405de34
1#nullable enable
2
3using System.Globalization;
4using System.Text;
5using System.Text.RegularExpressions;
6
7namespace CascadeIDE.Services.MarkdownPreview;
8
9/// <summary>GitHub-подобный slug для заголовков и fragment-навигации.</summary>
10public static partial class MarkdownPreviewHeadingSlug
11{
12 private static readonly Dictionary<string, int> SlugCounts = new(StringComparer.OrdinalIgnoreCase);
13
14 public static string Create(string headingText)
15 {
16 var baseSlug = Normalize(headingText);
17 if (baseSlug.Length == 0)
18 baseSlug = "section";
19
20 if (!SlugCounts.TryGetValue(baseSlug, out var count))
21 {
22 SlugCounts[baseSlug] = 1;
23 return baseSlug;
24 }
25
26 count++;
27 SlugCounts[baseSlug] = count;
28 return $"{baseSlug}-{count}";
29 }
30
31 public static void ResetSlugCounts() => SlugCounts.Clear();
32
33 private static string Normalize(string text)
34 {
35 var normalized = text.Normalize(NormalizationForm.FormKD);
36 var sb = new StringBuilder(normalized.Length);
37 var lastWasDash = false;
38
39 foreach (var ch in normalized)
40 {
41 if (char.GetUnicodeCategory(ch) == UnicodeCategory.NonSpacingMark)
42 continue;
43
44 if (char.IsLetterOrDigit(ch))
45 {
46 sb.Append(char.ToLowerInvariant(ch));
47 lastWasDash = false;
48 continue;
49 }
50
51 if (lastWasDash)
52 continue;
53
54 sb.Append('-');
55 lastWasDash = true;
56 }
57
58 return sb.ToString().Trim('-');
59 }
60
61 [GeneratedRegex(@"<a\s+[^>]*\bid\s*=\s*[""'](?<id>[^""']+)[""'][^>]*>", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
62 private static partial Regex HtmlAnchorIdRegex();
63
64 public static IEnumerable<string> ExtractHtmlAnchorIds(string? html)
65 {
66 if (string.IsNullOrWhiteSpace(html))
67 yield break;
68
69 foreach (Match match in HtmlAnchorIdRegex().Matches(html))
70 {
71 var id = match.Groups["id"].Value.Trim();
72 if (id.Length > 0)
73 yield return id;
74 }
75 }
76}
77
View only · write via MCP/CIDE