Handle mardown html elements parsing with sanitization
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
@@ -70,6 +71,62 @@ func (c *converter) convertChildren(n ast.Node) ([]Node, error) {
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// convertInlineChildren walks inline content and merges adjacent RawHTML + Text
|
||||
// segments so tags split by goldmark (e.g. <strong>, b, </strong>) form one HTML
|
||||
// fragment for sanitization and conversion.
|
||||
func (c *converter) convertInlineChildren(n ast.Node) ([]Node, error) {
|
||||
var nodes []Node
|
||||
|
||||
for ch := n.FirstChild(); ch != nil; {
|
||||
if ch.Kind() == ast.KindRawHTML {
|
||||
run, next := c.collectRawHTMLRun(ch)
|
||||
inodes, err := convertProseMirrorFromInlineHTML(run)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes = append(nodes, prependOuterMarks(copyMarks(c.marks), inodes)...)
|
||||
ch = next
|
||||
continue
|
||||
}
|
||||
|
||||
converted, err := c.convertNode(ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes = append(nodes, converted...)
|
||||
ch = ch.NextSibling()
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// collectRawHTMLRun concatenates a leading RawHTML node and following Text/String
|
||||
// and RawHTML siblings until a different node kind is seen. next is the first
|
||||
// sibling not consumed (or nil).
|
||||
func (c *converter) collectRawHTMLRun(start ast.Node) (run string, next ast.Node) {
|
||||
var buf bytes.Buffer
|
||||
ch := start
|
||||
for ch != nil {
|
||||
switch ch.Kind() {
|
||||
case ast.KindRawHTML:
|
||||
raw := ch.(*ast.RawHTML)
|
||||
for i := 0; i < raw.Segments.Len(); i++ {
|
||||
seg := raw.Segments.At(i)
|
||||
buf.Write(seg.Value(c.source))
|
||||
}
|
||||
case ast.KindText:
|
||||
t := ch.(*ast.Text)
|
||||
buf.Write(t.Segment.Value(c.source))
|
||||
case ast.KindString:
|
||||
buf.Write(ch.(*ast.String).Value)
|
||||
default:
|
||||
return buf.String(), ch
|
||||
}
|
||||
ch = ch.NextSibling()
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (c *converter) convertNode(n ast.Node) ([]Node, error) {
|
||||
switch n.Kind() {
|
||||
case ast.KindHeading:
|
||||
@@ -107,7 +164,7 @@ func (c *converter) convertNode(n ast.Node) ([]Node, error) {
|
||||
case ast.KindRawHTML:
|
||||
return c.convertRawHTML(n)
|
||||
case ast.KindHTMLBlock:
|
||||
return nil, nil
|
||||
return c.convertHTMLBlock(n.(*ast.HTMLBlock))
|
||||
default:
|
||||
if n.Kind() == goldmarkast.KindStrikethrough {
|
||||
return c.convertStrikethrough(n)
|
||||
@@ -117,7 +174,7 @@ func (c *converter) convertNode(n ast.Node) ([]Node, error) {
|
||||
}
|
||||
|
||||
func (c *converter) convertHeading(n *ast.Heading) ([]Node, error) {
|
||||
children, err := c.convertChildren(n)
|
||||
children, err := c.convertInlineChildren(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -135,7 +192,7 @@ func (c *converter) convertHeading(n *ast.Heading) ([]Node, error) {
|
||||
}
|
||||
|
||||
func (c *converter) convertParagraph(n ast.Node) ([]Node, error) {
|
||||
children, err := c.convertChildren(n)
|
||||
children, err := c.convertInlineChildren(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -334,7 +391,7 @@ func (c *converter) convertEmphasis(n *ast.Emphasis) ([]Node, error) {
|
||||
}
|
||||
|
||||
c.marks = append(c.marks, mark)
|
||||
children, err := c.convertChildren(n)
|
||||
children, err := c.convertInlineChildren(n)
|
||||
c.marks = c.marks[:len(c.marks)-1]
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -383,7 +440,7 @@ func (c *converter) convertLink(n *ast.Link) ([]Node, error) {
|
||||
}
|
||||
|
||||
c.marks = append(c.marks, Mark{Type: MarkLink, Attrs: attrs})
|
||||
children, err := c.convertChildren(n)
|
||||
children, err := c.convertInlineChildren(n)
|
||||
c.marks = c.marks[:len(c.marks)-1]
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -411,28 +468,48 @@ func (c *converter) convertAutoLink(n *ast.AutoLink) ([]Node, error) {
|
||||
}
|
||||
|
||||
func (c *converter) convertRawHTML(n ast.Node) ([]Node, error) {
|
||||
raw, ok := n.(*ast.RawHTML)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot convert raw html: unexpected node type %T", n)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < raw.Segments.Len(); i++ {
|
||||
seg := raw.Segments.At(i)
|
||||
buf.Write(seg.Value(c.source))
|
||||
}
|
||||
run := buf.String()
|
||||
if run == "" {
|
||||
return nil, nil
|
||||
}
|
||||
nodes, err := convertProseMirrorFromInlineHTML(run)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return prependOuterMarks(copyMarks(c.marks), nodes), nil
|
||||
}
|
||||
|
||||
func (c *converter) convertHTMLBlock(n *ast.HTMLBlock) ([]Node, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
for i := 0; i < n.Lines().Len(); i++ {
|
||||
line := n.Lines().At(i)
|
||||
buf.Write(line.Value(c.source))
|
||||
}
|
||||
if n.HasClosure() {
|
||||
buf.Write(n.ClosureLine.Value(c.source))
|
||||
}
|
||||
|
||||
content := buf.String()
|
||||
if content == "" {
|
||||
raw := buf.String()
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []Node{{
|
||||
Type: NodeText,
|
||||
Text: &content,
|
||||
Marks: copyMarks(c.marks),
|
||||
}}, nil
|
||||
return convertProseMirrorFromHTMLBlock(raw)
|
||||
}
|
||||
|
||||
func (c *converter) convertStrikethrough(n ast.Node) ([]Node, error) {
|
||||
c.marks = append(c.marks, Mark{Type: MarkStrike})
|
||||
children, err := c.convertChildren(n)
|
||||
children, err := c.convertInlineChildren(n)
|
||||
c.marks = c.marks[:len(c.marks)-1]
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -451,3 +528,18 @@ func copyMarks(marks []Mark) []Mark {
|
||||
|
||||
return cp
|
||||
}
|
||||
|
||||
// prependOuterMarks applies markdown inline context marks (e.g. emphasis around
|
||||
// raw HTML) to nodes produced from sanitized HTML. Images are left unchanged.
|
||||
func prependOuterMarks(outer []Mark, nodes []Node) []Node {
|
||||
if len(outer) == 0 {
|
||||
return nodes
|
||||
}
|
||||
for i := range nodes {
|
||||
if nodes[i].Type == NodeImage {
|
||||
continue
|
||||
}
|
||||
nodes[i].Marks = append(copyMarks(outer), nodes[i].Marks...)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user