mirror of https://github.com/abpframework/abp.git
Browse Source
Replaces the previous HTML-based table of contents (TOC) extraction using HtmlAgilityPack with a Markdig-based approach. Introduces custom Markdig extensions and renderers to extract headings directly from markdown, updates the TOC service and interface, and removes the HtmlAgilityPack dependency from the project.pull/23666/head
8 changed files with 177 additions and 113 deletions
@ -0,0 +1,73 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using Markdig.Renderers; |
|||
using Markdig.Renderers.Html; |
|||
using Markdig.Syntax; |
|||
using Markdig.Syntax.Inlines; |
|||
|
|||
namespace Volo.Docs.TableOfContents; |
|||
|
|||
public class CustomHeadingRenderer : MarkdownObjectRenderer<HtmlRenderer, HeadingBlock> |
|||
{ |
|||
private readonly HeadingExtractionExtension _extension; |
|||
private readonly HeadingRenderer _originalRenderer; |
|||
|
|||
public CustomHeadingRenderer(HeadingExtractionExtension extension, HeadingRenderer originalRenderer) |
|||
{ |
|||
_extension = extension; |
|||
_originalRenderer = originalRenderer ?? new HeadingRenderer(); |
|||
} |
|||
|
|||
protected override void Write(HtmlRenderer renderer, HeadingBlock headingBlock) |
|||
{ |
|||
var headingText = GetPlainText(headingBlock.Inline); |
|||
var headingId = headingBlock.TryGetAttributes()?.Id ?? string.Empty; |
|||
_extension.Headings.Add((headingBlock.Level, headingText, headingId)); |
|||
_originalRenderer.Write(renderer, headingBlock); |
|||
} |
|||
|
|||
private static string GetPlainText(ContainerInline container) |
|||
{ |
|||
if (container == null) |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
|
|||
var builder = new StringBuilder(); |
|||
|
|||
var inlinesToProcess = new Stack<Inline>(); |
|||
|
|||
// Push items in reverse for left-to-right processing (LIFO stack behavior)
|
|||
foreach (var inline in container.Reverse()) |
|||
{ |
|||
inlinesToProcess.Push(inline); |
|||
} |
|||
|
|||
while (inlinesToProcess.Count > 0) |
|||
{ |
|||
var currentInline = inlinesToProcess.Pop(); |
|||
|
|||
switch (currentInline) |
|||
{ |
|||
// Case 1: Simple leaf nodes with text content
|
|||
case LiteralInline literal: |
|||
builder.Append(literal.Content); |
|||
break; |
|||
case CodeInline code: |
|||
builder.Append(code.Content); |
|||
break; |
|||
|
|||
// Case 2: Container nodes - process their children next
|
|||
case ContainerInline childContainer: |
|||
foreach (var childInline in childContainer.Reverse()) |
|||
{ |
|||
inlinesToProcess.Push(childInline); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return builder.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System.Collections.Generic; |
|||
using Markdig; |
|||
using Markdig.Renderers; |
|||
using Markdig.Renderers.Html; |
|||
|
|||
namespace Volo.Docs.TableOfContents; |
|||
|
|||
public class HeadingExtractionExtension : IMarkdownExtension |
|||
{ |
|||
public List<(int Level, string Text, string Id)> Headings { get; } = []; |
|||
|
|||
public void Setup(MarkdownPipelineBuilder pipeline) |
|||
{ |
|||
} |
|||
|
|||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) |
|||
{ |
|||
if (renderer is not HtmlRenderer) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var originalHeadingRenderer = renderer.ObjectRenderers.Find<HeadingRenderer>(); |
|||
if (originalHeadingRenderer != null) |
|||
{ |
|||
renderer.ObjectRenderers.Remove(originalHeadingRenderer); |
|||
} |
|||
renderer.ObjectRenderers.Add(new CustomHeadingRenderer(this, originalHeadingRenderer)); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue