prosemirror html conversion unwrapping + table rows collection
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -225,6 +225,16 @@ func (c *htmlBlockConverter) convertBlockElement(n *html.Node) ([]Node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *htmlBlockConverter) unwrapBlockElement(n *html.Node) ([]Node, error) {
|
func (c *htmlBlockConverter) unwrapBlockElement(n *html.Node) ([]Node, error) {
|
||||||
|
if !hasBlockElementChild(n) {
|
||||||
|
inlines, err := c.convertInlineFragments(n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(inlines) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return []Node{{Type: NodeParagraph, Content: inlines}}, nil
|
||||||
|
}
|
||||||
return c.convertBlockChildren(n)
|
return c.convertBlockChildren(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,26 +405,46 @@ func hasBlockElementChild(n *html.Node) bool {
|
|||||||
func blockTagName(name string) bool {
|
func blockTagName(name string) bool {
|
||||||
switch name {
|
switch name {
|
||||||
case "p", "div", "blockquote", "pre", "ul", "ol", "table",
|
case "p", "div", "blockquote", "pre", "ul", "ol", "table",
|
||||||
"h1", "h2", "h3", "h4", "h5", "h6", "hr":
|
"h1", "h2", "h3", "h4", "h5", "h6", "hr",
|
||||||
|
"section", "article", "aside", "main", "header", "footer", "nav",
|
||||||
|
"figure", "center",
|
||||||
|
"html", "body", "head":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *htmlBlockConverter) convertTable(n *html.Node) ([]Node, error) {
|
// collectTableRows returns <tr> nodes that belong to this table only: direct
|
||||||
|
// children of thead/tbody/tfoot, or direct <tr> children of the table element
|
||||||
|
// (implicit tbody). It does not descend into cells or nested tables, so inner
|
||||||
|
// tables cannot contribute rows to the outer table.
|
||||||
|
func collectTableRows(table *html.Node) []*html.Node {
|
||||||
var rows []*html.Node
|
var rows []*html.Node
|
||||||
var collect func(*html.Node)
|
for ch := table.FirstChild; ch != nil; ch = ch.NextSibling {
|
||||||
collect = func(x *html.Node) {
|
if ch.Type != html.ElementNode {
|
||||||
if x.Type == html.ElementNode && x.Data == "tr" {
|
continue
|
||||||
rows = append(rows, x)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
for ch := x.FirstChild; ch != nil; ch = ch.NextSibling {
|
switch ch.Data {
|
||||||
collect(ch)
|
case "thead", "tbody", "tfoot":
|
||||||
|
for tr := ch.FirstChild; tr != nil; tr = tr.NextSibling {
|
||||||
|
if tr.Type == html.ElementNode && tr.Data == "tr" {
|
||||||
|
rows = append(rows, tr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
collect(n)
|
case "tr":
|
||||||
|
rows = append(rows, ch)
|
||||||
|
case "caption", "colgroup", "col":
|
||||||
|
// Table metadata / columns; not row containers.
|
||||||
|
default:
|
||||||
|
// Ignore other direct children (e.g. invalid markup).
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *htmlBlockConverter) convertTable(n *html.Node) ([]Node, error) {
|
||||||
|
rows := collectTableRows(n)
|
||||||
|
|
||||||
var rowNodes []Node
|
var rowNodes []Node
|
||||||
for _, tr := range rows {
|
for _, tr := range rows {
|
||||||
|
|||||||
@@ -406,6 +406,42 @@ func TestParseMarkdown_BlockHTML(t *testing.T) {
|
|||||||
assert.Equal(t, "block", *doc.Content[0].Content[0].Text)
|
assert.Equal(t, "block", *doc.Content[0].Content[0].Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseMarkdown_BlockHTMLDivPreservesInlineMarks(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
doc, err := ParseMarkdown("<div>hello <strong>world</strong></div>\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, doc.Content, 1)
|
||||||
|
p := doc.Content[0]
|
||||||
|
require.Equal(t, NodeParagraph, p.Type)
|
||||||
|
|
||||||
|
var foundStrong bool
|
||||||
|
for _, ch := range p.Content {
|
||||||
|
if ch.Type != NodeText || ch.Text == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *ch.Text != "world" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
require.Len(t, ch.Marks, 1)
|
||||||
|
assert.Equal(t, MarkStrong, ch.Marks[0].Type)
|
||||||
|
foundStrong = true
|
||||||
|
}
|
||||||
|
assert.True(t, foundStrong, "expected bold mark on 'world' inside a single paragraph")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMarkdown_BlockHTMLDivAroundSectionWithParagraphs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
doc, err := ParseMarkdown("<div><section><p>a</p><p>b</p></section></div>\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, doc.Content, 2)
|
||||||
|
assert.Equal(t, NodeParagraph, doc.Content[0].Type)
|
||||||
|
assert.Equal(t, NodeParagraph, doc.Content[1].Type)
|
||||||
|
assert.Equal(t, "a", *doc.Content[0].Content[0].Text)
|
||||||
|
assert.Equal(t, "b", *doc.Content[1].Content[0].Text)
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseMarkdown_BlockHTMLWithClosureLine(t *testing.T) {
|
func TestParseMarkdown_BlockHTMLWithClosureLine(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -467,6 +503,19 @@ func TestParseMarkdown_BlockHTMLTable(t *testing.T) {
|
|||||||
assert.Equal(t, 1, tdAttrs.Rowspan)
|
assert.Equal(t, 1, tdAttrs.Rowspan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseMarkdown_BlockHTMLNestedTableDoesNotHoistInnerRows(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
md := "<table><tr><td><table><tr><td>inner</td></tr></table></td></tr></table>\n"
|
||||||
|
doc, err := ParseMarkdown(md)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, doc.Content, 1)
|
||||||
|
assert.Equal(t, NodeTable, doc.Content[0].Type)
|
||||||
|
// Outer table must have exactly one row; inner <tr> must not become a second outer row.
|
||||||
|
require.Len(t, doc.Content[0].Content, 1)
|
||||||
|
assert.Equal(t, NodeTableRow, doc.Content[0].Content[0].Type)
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseMarkdown_BlockHTMLTableCellSpans(t *testing.T) {
|
func TestParseMarkdown_BlockHTMLTableCellSpans(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user