How to convert markdown to HTML in Golang with adding section tag

huangapple go评论75阅读模式
英文:

How to convert markdown to HTML in Golang with adding section tag

问题

我有以下的 Markdown 文本:

## Hello
### This is a test message
Ligisnfmkdfn

我使用 GO 模块 gomarkdown 将 Markdown 转换为带有 CommonExtensions 和 AutoHeadingIDs 解析器的 HTML,结果如下:

<h2 id="helo">Hello</h2>
<h3 id="this-is-a-test-message">This is a test message</h3>
<p>Ligisnfmkdfn</p>

请问我如何在 Node.js 中获得类似使用 markdown-it-header-sections 的结果呢?

<section id="helo">
   <h2>Hello</h2>
   <section id="this-is-a-test-message">
      <h3>This is a test message</h3>
      <p>Ligisnfmkdfn</p>
   </section>
</section>
英文:

I have the markdown below

## Hello
### This is a test message
Ligisnfmkdfn

And I use the GO module gomarkdown to convert markdown to HTML with CommonExtensions and AutoHeadingIDs parser and I got the result are

<h2 id="helo">Hello</h2>
<h3 id="this-is-a-test-message">This is a test message</h3>
<p>Ligisnfmkdfn</p>

How can I get the result like using markdown-it-header-sections in nodejs

<section id="helo">
   <h2>Hello</h2>
   <section id="this-is-a-test-message">
      <h3>This is a test message</h3>
      <p>Ligisnfmkdfn</p>
   </section>
</section>

答案1

得分: 4

这是一个相对完整的解决方案:

package main

import (
	"fmt"
	"io"
	"regexp"
	"strings"

	"github.com/gomarkdown/markdown"
	"github.com/gomarkdown/markdown/ast"
	"github.com/gomarkdown/markdown/html"
)

// levels 跟踪标题的深度结构
var levels []int

func hasLevels() bool {
	return len(levels) > 0
}

func lastLevel() int {
	if hasLevels() {
		return levels[len(levels)-1]
	}
	return 0
}

func popLevel() int {
	level := lastLevel()
	levels = levels[:len(levels)-1]
	return level
}

func pushLevel(x int) {
	levels = append(levels, x)
}

var reID = regexp.MustCompile(`\s+`)

// renderSections 捕获 ast.Heading 节点,并将节点及其“子”节点包装在 <section>...</section> 标签中;
// 在 Markdown 中没有真正的层次结构,所以我们通过以下方式构建一个层次结构:
// - H2 是 H1 的子节点,以此类推从 1 → 2 → 3 ... → N
// - H1 是另一个 H1 的同级节点
func renderSections(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
	openSection := func(level int, id string) {
		w.Write([]byte(fmt.Sprintf("<section id=\"%s\">\n", id)))
		pushLevel(level)
	}
	closeSection := func() {
		w.Write([]byte("</section>\n"))
		popLevel()
	}

	if _, ok := node.(*ast.Heading); ok {
		level := node.(*ast.Heading).Level
		if entering {
			// 关闭比当前级别更深的标题-节;我们已经“上升”了一定数量的级别
			for lastLevel() > level {
				closeSection()
			}

			txtNode := node.GetChildren()[0]
			if _, ok := txtNode.(*ast.Text); !ok {
				panic(fmt.Errorf("expected txtNode to be *ast.Text; got %T", txtNode))
			}
			headTxt := string(txtNode.AsLeaf().Literal)
			id := strings.ToLower(reID.ReplaceAllString(headTxt, "-"))

			openSection(level, id)
		}
	}

	// 在文档末尾
	if _, ok := node.(*ast.Document); ok {
		if !entering {
			for hasLevels() {
				closeSection()
			}
		}
	}

	// 继续正常处理
	return ast.GoToNext, false
}

func main() {
	lines := []string{
		"## Hello",
		"### This is a test message",
		"Ligisnfmkdfn",
	}
	md := strings.Join(lines, "\n")

	opts := html.RendererOptions{
		Flags:          html.CommonFlags,
		RenderNodeHook: renderSections,
	}
	renderer := html.NewRenderer(opts)

	html := markdown.ToHTML([]byte(md), nil, renderer)

	fmt.Println(string(html))
}

运行上述代码,输出结果为:

<section id="hello">
  <h2>Hello</h2>
  <section id="this-is-a-test-message">
    <h3>This is a test message</h3>
    <p>Ligisnfmkdfn</p>
  </section>
</section>

我称之为“相对完整”,因为它足够智能地处理以下输入:

lines := []string{
    "# H1α",
    "## H2A",
    "## H2B",
    "## H2C",
    "### H31",
    "#### H4I",
    "## H2D",
    "# H1β",
    "## H2E",
}

并生成以下输出:

<section id="h1α">
  <h1>H1α</h1>
  <section id="h2a">
    <h2>H2A</h2>
  </section>
  <section id="h2b">
    <h2>H2B</h2>
  </section>
  <section id="h2c">
    <h2>H2C</h2>
    <section id="h31">
      <h3>H31</h3>
      <section id="h4i">
        <h4>H4I</h4>
      </section>
    </section>
  </section>
  <section id="h2d">
    <h2>H2D</h2>
  </section>
</section>
<section id="h1β">
  <h1>H1β</h1>
  <section id="h2e">
    <h2>H2E</h2>
  </section>
</section>

但我没有进行严格测试,所以不确定它在哪些方面可能不符合预期。

英文:

Here's a moderately complete solution:

package main

import (
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;regexp&quot;
	&quot;strings&quot;

	&quot;github.com/gomarkdown/markdown&quot;
	&quot;github.com/gomarkdown/markdown/ast&quot;
	&quot;github.com/gomarkdown/markdown/html&quot;
)

// levels tracks how deep we are in a heading &quot;structure&quot;
var levels []int

func hasLevels() bool {
	return len(levels) &gt; 0
}

func lastLevel() int {
	if hasLevels() {
		return levels[len(levels)-1]
	}
	return 0
}

func popLevel() int {
	level := lastLevel()
	levels = levels[:len(levels)-1]
	return level
}

func pushLevel(x int) {
	levels = append(levels, x)
}

var reID = regexp.MustCompile(`\s+`)

// renderSections catches an ast.Heading node, and wraps the node
// and its &quot;children&quot; nodes in &lt;section&gt;...&lt;/section&gt; tags; there&#39;s no
// real hierarchy in Markdown, so we make one up by saying things like:
// - H2 is a child of H1, and so forth from 1 → 2 → 3 ... → N
// - an H1 is a sibling of another H1
func renderSections(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
	openSection := func(level int, id string) {
		w.Write([]byte(fmt.Sprintf(&quot;&lt;section id=\&quot;%s\&quot;&gt;\n&quot;, id)))
		pushLevel(level)
	}
	closeSection := func() {
		w.Write([]byte(&quot;&lt;/section&gt;\n&quot;))
		popLevel()
	}

	if _, ok := node.(*ast.Heading); ok {
		level := node.(*ast.Heading).Level
		if entering {
			// close heading-sections deeper than this level; we&#39;ve &quot;come up&quot; some number of levels
			for lastLevel() &gt; level {
				closeSection()
			}

			txtNode := node.GetChildren()[0]
			if _, ok := txtNode.(*ast.Text); !ok {
				panic(fmt.Errorf(&quot;expected txtNode to be *ast.Text; got %T&quot;, txtNode))
			}
			headTxt := string(txtNode.AsLeaf().Literal)
			id := strings.ToLower(reID.ReplaceAllString(headTxt, &quot;-&quot;))

			openSection(level, id)
		}
	}

	// at end of document
	if _, ok := node.(*ast.Document); ok {
		if !entering {
			for hasLevels() {
				closeSection()
			}
		}
	}

	// continue as normal
	return ast.GoToNext, false
}

func main() {
	lines := []string{
		&quot;## Hello&quot;,
		&quot;### This is a test message&quot;,
		&quot;Ligisnfmkdfn&quot;,
	}
	md := strings.Join(lines, &quot;\n&quot;)

	opts := html.RendererOptions{
		Flags:          html.CommonFlags,
		RenderNodeHook: renderSections,
	}
	renderer := html.NewRenderer(opts)

	html := markdown.ToHTML([]byte(md), nil, renderer)

	fmt.Println(string(html))
}

When I run that, I get:

&lt;section id=&quot;hello&quot;&gt;
  &lt;h2&gt;Hello&lt;/h2&gt;
  &lt;section id=&quot;this-is-a-test-message&quot;&gt;
    &lt;h3&gt;This is a test message&lt;/h3&gt;
    &lt;p&gt;Ligisnfmkdfn&lt;/p&gt;
  &lt;/section&gt;
&lt;/section&gt;

I say it's moderately complete because it's smart enough to deal with input like this:

lines := []string{
    &quot;# H1α&quot;,
    &quot;## H2A&quot;,
    &quot;## H2B&quot;,
    &quot;## H2C&quot;,
    &quot;### H31&quot;,
    &quot;#### H4I&quot;,
    &quot;## H2D&quot;,
    &quot;# H1β&quot;,
    &quot;## H2E&quot;,
}

and it produces:

&lt;section id=&quot;h1α&quot;&gt;
  &lt;h1&gt;H1α&lt;/h1&gt;
  &lt;section id=&quot;h2a&quot;&gt;
    &lt;h2&gt;H2A&lt;/h2&gt;
  &lt;/section&gt;
  &lt;section id=&quot;h2b&quot;&gt;
    &lt;h2&gt;H2B&lt;/h2&gt;
  &lt;/section&gt;
  &lt;section id=&quot;h2c&quot;&gt;
    &lt;h2&gt;H2C&lt;/h2&gt;
    &lt;section id=&quot;h31&quot;&gt;
      &lt;h3&gt;H31&lt;/h3&gt;
      &lt;section id=&quot;h4i&quot;&gt;
        &lt;h4&gt;H4I&lt;/h4&gt;
      &lt;/section&gt;
    &lt;/section&gt;
  &lt;/section&gt;
  &lt;section id=&quot;h2d&quot;&gt;
    &lt;h2&gt;H2D&lt;/h2&gt;
  &lt;/section&gt;
&lt;/section&gt;
&lt;section id=&quot;h1β&quot;&gt;
  &lt;h1&gt;H1β&lt;/h1&gt;
  &lt;section id=&quot;h2e&quot;&gt;
    &lt;h2&gt;H2E&lt;/h2&gt;
  &lt;/section&gt;
&lt;/section&gt;

But I haven't rigorously tested this, so I'm not sure where it might not meet expectations.

huangapple
  • 本文由 发表于 2022年9月16日 18:10:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/73743233.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定