Switch to goldmark parser

This commit is contained in:
Christian Muehlhaeuser
2019-12-13 03:00:45 +01:00
parent 30ed5cc5ef
commit 07f52dd42a
9 changed files with 397 additions and 256 deletions
+25
View File
@@ -0,0 +1,25 @@
package gold
import (
"io"
)
type CheckedItemElement struct {
Checked bool
}
func (e *CheckedItemElement) Render(w io.Writer, ctx RenderContext) error {
var el *BaseElement
pre := "✗ "
if e.Checked {
pre = "✓ "
}
el = &BaseElement{
Prefix: pre,
Style: ctx.style[CheckedItem],
}
return el.Render(w, ctx)
}
+202 -130
View File
@@ -1,9 +1,12 @@
package gold
import (
"bytes"
"fmt"
"io"
bf "gopkg.in/russross/blackfriday.v2"
"github.com/yuin/goldmark/ast"
astext "github.com/yuin/goldmark/extension/ast"
)
type ElementRenderer interface {
@@ -21,210 +24,279 @@ type Element struct {
Finisher ElementFinisher
}
func (tr *TermRenderer) NewElement(node *bf.Node) Element {
func (tr *TermRenderer) NewElement(node ast.Node, source []byte) Element {
ctx := tr.context
// fmt.Print(strings.Repeat(" ", ctx.blockStack.Len()), node.Type(), node.Kind())
// defer fmt.Println()
switch node.Type {
case bf.Document:
de := &DocumentElement{}
switch node.Kind() {
// Document
case ast.KindDocument:
return Element{
Renderer: de,
Finisher: de,
Renderer: &DocumentElement{},
Finisher: &DocumentElement{},
}
// Heading
case ast.KindHeading:
n := node.(*ast.Heading)
he := &HeadingElement{
Level: n.Level,
First: node.PreviousSibling() == nil,
}
return Element{
Exiting: "",
Renderer: he,
Finisher: he,
}
// Paragraph
case ast.KindParagraph:
return Element{
Renderer: &ParagraphElement{},
Finisher: &ParagraphElement{},
}
// Blockquote
case ast.KindBlockquote:
e := &BlockElement{
Block: &bytes.Buffer{},
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.style[BlockQuote], true),
Margin: true,
}
case bf.BlockQuote:
return Element{
Entering: "\n",
Exiting: "\n",
Renderer: &BaseElement{
Token: string(node.Literal),
Style: ctx.style[BlockQuote],
},
Renderer: e,
Finisher: e,
}
case bf.List:
le := &ListElement{
Nested: node.Parent.Type == bf.Item,
// Lists
case ast.KindList:
e := &BlockElement{
Block: &bytes.Buffer{},
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.style[List], true),
Margin: true,
}
return Element{
Renderer: le,
Finisher: le,
Entering: "\n",
Renderer: e,
Finisher: e,
}
case bf.Item:
case ast.KindListItem:
var l uint
if node.ListData.ListFlags&bf.ListTypeOrdered > 0 {
l = 1
n := node
for n.Prev != nil && (n.Prev.Type == bf.Item) {
l++
n = n.Prev
var e uint
l = 1
n := node
for n.PreviousSibling() != nil && (n.PreviousSibling().Kind() == ast.KindListItem) {
l++
n = n.PreviousSibling()
}
if node.Parent().(*ast.List).IsOrdered() {
e = l
}
post := "\n"
if node.LastChild().Kind() == ast.KindList || node.NextSibling() == nil {
post = ""
}
if node.FirstChild().FirstChild().Kind() == astext.KindTaskCheckBox {
nc := node.FirstChild().FirstChild().(*astext.TaskCheckBox)
return Element{
Exiting: post,
Renderer: &CheckedItemElement{
Checked: nc.IsChecked,
},
}
}
return Element{
Exiting: post,
Renderer: &ItemElement{
Text: string(node.Literal),
Enumeration: l,
Enumeration: e,
},
}
case bf.Paragraph:
pe := &ParagraphElement{
InsideList: node.Parent != nil && node.Parent.Type == bf.Item,
// Text Elements
case ast.KindText:
n := node.(*ast.Text)
s := string(n.Segment.Value(source))
if n.HardLineBreak() || (n.SoftLineBreak()) {
s += "\n"
}
return Element{
Renderer: pe,
Finisher: pe,
}
case bf.Heading:
return Element{
Exiting: "\n",
Renderer: &HeadingElement{
Text: string(node.FirstChild.Literal),
Level: node.HeadingData.Level,
First: node.Prev == nil,
Renderer: &BaseElement{
Token: ctx.SanitizeHTML(s, false),
Style: ctx.style[Text],
},
}
case bf.HorizontalRule:
case ast.KindEmphasis:
n := node.(*ast.Emphasis)
s := string(n.Text(source))
style := ctx.style[Emph]
if n.Level > 1 {
style = ctx.style[Strong]
}
return Element{
Renderer: &BaseElement{
Token: ctx.SanitizeHTML(s, false),
Style: style,
},
}
case ast.KindThematicBreak:
return Element{
Entering: "",
Exiting: "\n",
Exiting: "",
Renderer: &BaseElement{
Style: ctx.style[HorizontalRule],
},
}
case bf.Emph:
return Element{
Renderer: &BaseElement{
Token: string(node.FirstChild.Literal),
Style: ctx.style[Emph],
},
}
case bf.Strong:
return Element{
Renderer: &BaseElement{
Token: string(node.FirstChild.Literal),
Style: ctx.style[Strong],
},
}
case bf.Del:
return Element{
Renderer: &BaseElement{
Token: string(node.Literal),
Style: ctx.style[Del],
},
}
case bf.Link:
var text string
if node.LastChild != nil {
text = string(node.LastChild.Literal)
}
// Links
case ast.KindLink:
n := node.(*ast.Link)
text := string(n.Text(source))
return Element{
Renderer: &LinkElement{
Text: text,
BaseURL: tr.context.options.BaseURL,
URL: string(node.LinkData.Destination),
BaseURL: ctx.options.BaseURL,
URL: string(n.Destination),
},
}
case bf.Image:
var text string
if node.LastChild != nil {
text = string(node.LastChild.Literal)
case ast.KindAutoLink:
n := node.(*ast.AutoLink)
text := string(n.Text(source))
return Element{
Renderer: &LinkElement{
Text: text,
BaseURL: ctx.options.BaseURL,
URL: string(n.URL(source)),
},
}
// Images
case ast.KindImage:
n := node.(*ast.Image)
text := string(n.Text(source))
return Element{
Renderer: &ImageElement{
Text: text,
BaseURL: tr.context.options.BaseURL,
URL: string(node.LinkData.Destination),
BaseURL: ctx.options.BaseURL,
URL: string(n.Destination),
},
}
case bf.Text:
return Element{
Renderer: &BaseElement{
Token: tr.context.SanitizeHTML(string(node.Literal), false),
Style: ctx.style[Text],
},
// Code
case ast.KindFencedCodeBlock:
n := node.(*ast.FencedCodeBlock)
l := n.Lines().Len()
s := ""
for i := 0; i < l; i++ {
line := n.Lines().At(i)
s += string(line.Value(source))
}
case bf.HTMLBlock:
return Element{
Renderer: &BaseElement{
Token: tr.context.SanitizeHTML(string(node.Literal), true) + "\n",
Style: ctx.style[HTMLBlock],
},
}
case bf.CodeBlock:
return Element{
Entering: "\n",
Renderer: &CodeBlockElement{
Code: string(node.Literal),
Language: string(node.CodeBlockData.Info),
Code: s,
Language: string(n.Language(source)),
},
}
case bf.Softbreak:
case ast.KindCodeBlock:
n := node.(*ast.CodeBlock)
l := n.Lines().Len()
s := ""
for i := 0; i < l; i++ {
line := n.Lines().At(i)
s += string(line.Value(source))
}
return Element{
Exiting: "\n",
Renderer: &BaseElement{
Token: string(node.Literal),
Style: ctx.style[Softbreak],
Entering: "\n",
Renderer: &CodeBlockElement{
Code: s,
},
}
case bf.Hardbreak:
case ast.KindCodeSpan:
// n := node.(*ast.CodeSpan)
e := &BlockElement{
Block: &bytes.Buffer{},
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.style[Code], true),
}
return Element{
Exiting: "\n",
Renderer: &BaseElement{
Token: string(node.Literal),
Style: ctx.style[Hardbreak],
},
Renderer: e,
Finisher: e,
}
case bf.Code:
return Element{
Renderer: &BaseElement{
Token: string(node.Literal),
Style: ctx.style[Code],
},
}
case bf.HTMLSpan:
return Element{
Renderer: &BaseElement{
Token: tr.context.SanitizeHTML(string(node.Literal), true) + "\n",
Style: ctx.style[HTMLSpan],
},
}
case bf.Table:
// Tables
case astext.KindTable:
te := &TableElement{}
return Element{
Entering: "\n",
Renderer: te,
Finisher: te,
}
case bf.TableCell:
case astext.KindTableCell:
s := ""
n := node.FirstChild
n := node.FirstChild()
for n != nil {
s += string(n.Literal)
s += string(n.LinkData.Destination)
n = n.Next
s += string(n.Text(source))
// s += string(n.LinkData.Destination)
n = n.NextSibling()
}
return Element{
Renderer: &TableCellElement{
Text: s,
Head: node.Parent.Parent.Type == bf.TableHead,
Head: node.Parent().Kind() == astext.KindTableHeader,
},
}
case bf.TableHead:
case astext.KindTableHeader:
return Element{
Finisher: &TableHeadElement{},
}
case bf.TableBody:
return Element{}
case bf.TableRow:
case astext.KindTableRow:
return Element{
Finisher: &TableRowElement{},
}
default:
// HTML Elements
case ast.KindHTMLBlock:
n := node.(*ast.HTMLBlock)
return Element{
Renderer: &BaseElement{
Token: string(node.Literal),
Token: ctx.SanitizeHTML(string(n.Text(source)), true) + "\n",
Style: ctx.style[HTMLBlock],
},
}
case ast.KindRawHTML:
n := node.(*ast.RawHTML)
return Element{
Renderer: &BaseElement{
Token: ctx.SanitizeHTML(string(n.Text(source)), true) + "\n",
Style: ctx.style[HTMLSpan],
},
}
// Handled by parents
case astext.KindTaskCheckBox:
// handled by KindListItem
return Element{}
case ast.KindTextBlock:
return Element{}
// Unknown case
default:
fmt.Println("Warning: unhandled element", node.Kind().String())
return Element{}
}
}
+1
View File
@@ -14,5 +14,6 @@ require (
github.com/rakyll/statik v0.1.6
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/spf13/cobra v0.0.5
github.com/yuin/goldmark v1.1.14
gopkg.in/russross/blackfriday.v2 v2.0.0
)
+2
View File
@@ -87,6 +87,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.14 h1:9/OvYI+gdtQ5EAZY0y4kuVnuKjlE03BRqTw/njWYRNo=
github.com/yuin/goldmark v1.1.14/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+85 -17
View File
@@ -1,6 +1,7 @@
package gold
import (
"bytes"
"encoding/json"
"fmt"
"io"
@@ -8,6 +9,13 @@ import (
"net/url"
"strings"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
astext "github.com/yuin/goldmark/extension/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/util"
bf "gopkg.in/russross/blackfriday.v2"
)
@@ -83,21 +91,83 @@ func (tr *TermRenderer) Render(in string) string {
// RenderBytes returns the markdown rendered into a byte slice.
func (tr *TermRenderer) RenderBytes(in []byte) []byte {
return bf.Run(in, bf.WithRenderer(tr))
md := goldmark.New(
goldmark.WithExtensions(extension.GFM),
goldmark.WithParserOptions(
parser.WithAutoHeadingID(),
),
)
md.SetRenderer(
renderer.NewRenderer(
renderer.WithNodeRenderers(util.Prioritized(tr, 1000))))
var buf bytes.Buffer
if err := md.Convert(in, &buf); err != nil {
panic(err)
}
return buf.Bytes()
}
// RenderNode renders a single markdown node.
func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf.WalkStatus {
// RegisterFuncs implements NodeRenderer.RegisterFuncs .
func (r *TermRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
// blocks
reg.Register(ast.KindDocument, r.renderNode)
reg.Register(ast.KindHeading, r.renderNode)
reg.Register(ast.KindBlockquote, r.renderNode)
reg.Register(ast.KindCodeBlock, r.renderNode)
reg.Register(ast.KindFencedCodeBlock, r.renderNode)
reg.Register(ast.KindHTMLBlock, r.renderNode)
reg.Register(ast.KindList, r.renderNode)
reg.Register(ast.KindListItem, r.renderNode)
reg.Register(ast.KindParagraph, r.renderNode)
reg.Register(ast.KindTextBlock, r.renderNode)
reg.Register(ast.KindThematicBreak, r.renderNode)
// inlines
reg.Register(ast.KindAutoLink, r.renderNode)
reg.Register(ast.KindCodeSpan, r.renderNode)
reg.Register(ast.KindEmphasis, r.renderNode)
reg.Register(ast.KindImage, r.renderNode)
reg.Register(ast.KindLink, r.renderNode)
reg.Register(ast.KindRawHTML, r.renderNode)
reg.Register(ast.KindText, r.renderNode)
reg.Register(ast.KindString, r.renderNode)
// tables
reg.Register(astext.KindTable, r.renderNode)
reg.Register(astext.KindTableHeader, r.renderNode)
reg.Register(astext.KindTableRow, r.renderNode)
reg.Register(astext.KindTableCell, r.renderNode)
// definitions
reg.Register(astext.KindDefinitionList, r.renderNode)
reg.Register(astext.KindDefinitionTerm, r.renderNode)
reg.Register(astext.KindDefinitionDescription, r.renderNode)
// footnotes
reg.Register(astext.KindFootnote, r.renderNode)
reg.Register(astext.KindFootnoteList, r.renderNode)
reg.Register(astext.KindFootnoteLink, r.renderNode)
reg.Register(astext.KindFootnoteBackLink, r.renderNode)
// checkboxes
reg.Register(astext.KindTaskCheckBox, r.renderNode)
// strikethrough
reg.Register(astext.KindStrikethrough, r.renderNode)
}
func (tr *TermRenderer) renderNode(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
// _, _ = w.Write([]byte(node.Type.String()))
writeTo := w
writeTo := io.Writer(w)
bs := tr.context.blockStack
// children get rendered by their parent
if isChild(node) {
return bf.GoToNext
return ast.WalkContinue, nil
}
e := tr.NewElement(node)
e := tr.NewElement(node, source)
if entering {
// everything below the Document element gets rendered into a block buffer
if bs.Len() > 0 {
@@ -108,8 +178,7 @@ func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf
if e.Renderer != nil {
err := e.Renderer.Render(writeTo, tr.context)
if err != nil {
fmt.Println(err)
return bf.Terminate
return ast.WalkStop, err
}
}
} else {
@@ -120,21 +189,20 @@ func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf
// if we're finished rendering the entire document,
// flush to the real writer
if node.Type == bf.Document {
if node.Type() == ast.TypeDocument {
writeTo = w
}
if e.Finisher != nil {
err := e.Finisher.Finish(writeTo, tr.context)
if err != nil {
fmt.Println(err)
return bf.Terminate
return ast.WalkStop, err
}
}
_, _ = writeTo.Write([]byte(e.Exiting))
_, _ = bs.Current().Block.Write([]byte(e.Exiting))
}
return bf.GoToNext
return ast.WalkContinue, nil
}
// RenderHeader renders the markdown's header.
@@ -145,14 +213,14 @@ func (tr *TermRenderer) RenderHeader(w io.Writer, ast *bf.Node) {
func (tr *TermRenderer) RenderFooter(w io.Writer, ast *bf.Node) {
}
func isChild(node *bf.Node) bool {
if node.Parent == nil {
func isChild(node ast.Node) bool {
if node.Parent() == nil {
return false
}
// These types are already rendered by their parent
switch node.Parent.Type {
case bf.Heading, bf.Link, bf.Image, bf.TableCell, bf.Emph, bf.Strong:
switch node.Parent().Kind() {
case ast.KindLink, ast.KindImage, ast.KindEmphasis, ast.KindBlockquote, astext.KindTableCell:
return true
default:
return false
+33 -15
View File
@@ -1,13 +1,13 @@
package gold
import (
"bytes"
"io"
"github.com/muesli/reflow"
)
type HeadingElement struct {
Text string
Level int
First bool
}
@@ -31,6 +31,25 @@ func (e *HeadingElement) Render(w io.Writer, ctx RenderContext) error {
rules = cascadeStyles(false, rules, ctx.style[H6])
}
if !e.First {
renderText(w, bs.Current().Style, "\n")
}
be := BlockElement{
Block: &bytes.Buffer{},
Style: cascadeStyle(bs.Current().Style, rules, true),
}
bs.Push(be)
renderText(w, bs.Parent().Style, rules.Prefix)
renderText(w, bs.Current().Style, rules.StyledPrefix)
return nil
}
func (e *HeadingElement) Finish(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
rules := bs.Current().Style
var indent uint
var margin uint
if rules.Indent != nil {
@@ -43,7 +62,7 @@ func (e *HeadingElement) Render(w io.Writer, ctx RenderContext) error {
iw := &IndentWriter{
Indent: indent + margin,
IndentFunc: func(wr io.Writer) {
renderText(w, bs.Current().Style, " ")
renderText(w, bs.Parent().Style, " ")
},
Forward: &AnsiWriter{
Forward: w,
@@ -51,22 +70,21 @@ func (e *HeadingElement) Render(w io.Writer, ctx RenderContext) error {
}
flow := reflow.NewReflow(int(bs.Width(ctx) - indent - margin*2))
_, err := flow.Write(bs.Current().Block.Bytes())
if err != nil {
return err
}
flow.Close()
var pre string
if !e.First {
pre = "\n"
}
el := &BaseElement{
Prefix: pre,
Token: e.Text,
Style: rules,
}
err := el.Render(flow, ctx)
_, err = iw.Write(flow.Bytes())
if err != nil {
return err
}
flow.Close()
_, err = iw.Write(flow.Bytes())
return err
renderText(w, bs.Current().Style, rules.StyledSuffix)
renderText(w, bs.Parent().Style, rules.Suffix)
bs.Current().Block.Reset()
bs.Pop()
return nil
}
-46
View File
@@ -1,46 +0,0 @@
package gold
import (
"bytes"
"io"
"github.com/muesli/reflow"
)
type ListElement struct {
Nested bool
}
func (e *ListElement) Render(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
rules := ctx.style[List]
if !e.Nested {
_, _ = w.Write([]byte("\n"))
}
be := BlockElement{
Block: &bytes.Buffer{},
Style: cascadeStyle(bs.Current().Style, rules, true),
}
bs.Push(be)
return nil
}
func (e *ListElement) Finish(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
rules := bs.Current().Style
renderText(bs.Current().Block, rules, rules.Suffix)
_, err := NewMarginWriter(ctx, w, rules).Write(
reflow.Bytes(bs.Current().Block.Bytes(), int(bs.Width(ctx))))
if err != nil {
return err
}
bs.Current().Block.Reset()
bs.Pop()
return nil
}
+7 -20
View File
@@ -9,25 +9,19 @@ import (
)
type ParagraphElement struct {
InsideList bool
}
func (e *ParagraphElement) Render(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
var rules ElementStyle
if e.InsideList {
// list item
rules = ctx.style[List]
} else {
rules = ctx.style[Paragraph]
_, _ = w.Write([]byte("\n"))
be := BlockElement{
Block: &bytes.Buffer{},
Style: cascadeStyle(bs.Current().Style, rules, true),
}
bs.Push(be)
rules = ctx.style[Paragraph]
_, _ = w.Write([]byte("\n"))
be := BlockElement{
Block: &bytes.Buffer{},
Style: cascadeStyle(bs.Current().Style, rules, true),
}
bs.Push(be)
renderText(w, bs.Current().Style, rules.Prefix)
return nil
@@ -38,11 +32,6 @@ func (e *ParagraphElement) Finish(w io.Writer, ctx RenderContext) error {
rules := bs.Current().Style
keepNewlines := false
if e.InsideList {
// remove indent & margin for list items
rules = bs.Current().Style
keepNewlines = true
}
renderText(bs.Current().Block, rules, rules.Suffix)
@@ -61,8 +50,6 @@ func (e *ParagraphElement) Finish(w io.Writer, ctx RenderContext) error {
}
bs.Current().Block.Reset()
if !e.InsideList {
bs.Pop()
}
bs.Pop()
return nil
}
+42 -28
View File
@@ -16,38 +16,54 @@ type StyleType int
const (
Document StyleType = iota
// Block elements
Heading
Paragraph
BlockQuote
List
// Text elements
Text
Emph
Strong
HorizontalRule
// ListItem styles
Item
CheckedItem
Enumeration
Paragraph
Heading
// Links
Link
LinkText
// Images
Image
ImageText
// Code styles
CodeBlock
Code
// Table styles
Table
TableCell
TableHead
TableBody
TableRow
// HTML styles
HTMLBlock
HTMLSpan
// Style definitions for individual heading levels
H1
H2
H3
H4
H5
H6
HorizontalRule
Emph
Strong
Del
Link
LinkText
Image
ImageText
Text
HTMLBlock
CodeBlock
Softbreak
Hardbreak
Code
HTMLSpan
Table
TableCell
TableHead
TableBody
TableRow
)
type ElementStyle struct {
@@ -67,6 +83,8 @@ type ElementStyle struct {
Theme string `json:"theme"`
Prefix string `json:"prefix"`
Suffix string `json:"suffix"`
StyledPrefix string `json:"styled_prefix"`
StyledSuffix string `json:"styled_suffix"`
Format string `json:"format"`
}
@@ -235,6 +253,8 @@ func keyToType(key string) (StyleType, error) {
return List, nil
case "item":
return Item, nil
case "checked_item":
return CheckedItem, nil
case "enumeration":
return Enumeration, nil
case "paragraph":
@@ -259,8 +279,6 @@ func keyToType(key string) (StyleType, error) {
return Emph, nil
case "strong":
return Strong, nil
case "del":
return Del, nil
case "link":
return Link, nil
case "link_text":
@@ -275,10 +293,6 @@ func keyToType(key string) (StyleType, error) {
return HTMLBlock, nil
case "code_block":
return CodeBlock, nil
case "softbreak":
return Softbreak, nil
case "hardbreak":
return Hardbreak, nil
case "code":
return Code, nil
case "html_span":