-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHeadingExtractor.cs
More file actions
71 lines (61 loc) · 1.99 KB
/
Copy pathHeadingExtractor.cs
File metadata and controls
71 lines (61 loc) · 1.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using ShellDocs.Core;
namespace ShellDocs.Markdown;
internal static class HeadingExtractor
{
private static readonly Regex NonAlphaNumericRun = new("[^a-z0-9]+", RegexOptions.Compiled);
public static IReadOnlyList<Heading> Extract(MarkdownDocument document)
{
var used = new Dictionary<string, int>(StringComparer.Ordinal);
var headings = new List<Heading>();
foreach (var block in document.Descendants<HeadingBlock>())
{
var text = ExtractText(block.Inline).Trim();
if (text.Length == 0) continue;
var id = Slugify(text, used);
headings.Add(new Heading(block.Level, text, id));
}
return headings;
}
private static string ExtractText(ContainerInline? inline)
{
if (inline is null) return "";
var sb = new StringBuilder();
Walk(inline, sb);
return sb.ToString();
}
private static void Walk(Inline inline, StringBuilder sb)
{
switch (inline)
{
case LiteralInline lit:
sb.Append(lit.Content.ToString());
break;
case CodeInline code:
sb.Append(code.Content);
break;
case LineBreakInline:
sb.Append(' ');
break;
case ContainerInline container:
foreach (var child in container) Walk(child, sb);
break;
}
}
private static string Slugify(string text, Dictionary<string, int> used)
{
var lower = text.ToLowerInvariant();
var slug = NonAlphaNumericRun.Replace(lower, "-").Trim('-');
if (slug.Length == 0) slug = "section";
if (used.TryGetValue(slug, out var count))
{
used[slug] = count + 1;
return $"{slug}-{count + 1}";
}
used[slug] = 1;
return slug;
}
}