Convert html blocks inside table cells

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-04-01 21:32:39 +04:00
parent 9baeab4889
commit 7afab9487c
2 changed files with 124 additions and 2 deletions

View File

@@ -580,7 +580,7 @@ func (c *converter) convertTableCells(row ast.Node, cellType NodeType) ([]Node,
continue
}
inlineContent, err := c.convertInlineChildren(child)
content, err := c.convertTableCellContent(child)
if err != nil {
return nil, err
}
@@ -598,13 +598,86 @@ func (c *converter) convertTableCells(row ast.Node, cellType NodeType) ([]Node,
cells = append(cells, Node{
Type: cellType,
Attrs: attrs,
Content: []Node{{Type: NodeParagraph, Content: inlineContent}},
Content: content,
})
}
return cells, nil
}
func (c *converter) convertTableCellContent(cell ast.Node) ([]Node, error) {
if c.cellHasBlockHTML(cell) {
raw := c.collectCellRawContent(cell)
nodes, err := convertProseMirrorFromHTMLBlock(raw)
if err != nil {
return nil, err
}
if len(nodes) > 0 {
return nodes, nil
}
}
inlineContent, err := c.convertInlineChildren(cell)
if err != nil {
return nil, err
}
return []Node{{Type: NodeParagraph, Content: inlineContent}}, nil
}
func (c *converter) cellHasBlockHTML(cell ast.Node) bool {
for ch := cell.FirstChild(); ch != nil; ch = ch.NextSibling() {
if ch.Kind() != ast.KindRawHTML {
continue
}
raw := ch.(*ast.RawHTML)
for i := 0; i < raw.Segments.Len(); i++ {
seg := raw.Segments.At(i)
val := strings.ToLower(string(seg.Value(c.source)))
if containsBlockOpenTag(val) {
return true
}
}
}
return false
}
func containsBlockOpenTag(s string) bool {
for _, tag := range []string{
"<ul", "<ol", "<table", "<blockquote", "<pre", "<div",
"<h1", "<h2", "<h3", "<h4", "<h5", "<h6", "<hr",
} {
if strings.Contains(s, tag) {
return true
}
}
return false
}
func (c *converter) collectCellRawContent(cell ast.Node) string {
var buf bytes.Buffer
for ch := cell.FirstChild(); ch != nil; ch = ch.NextSibling() {
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))
if t.SoftLineBreak() {
buf.WriteByte(' ')
}
case ast.KindString:
buf.Write(ch.(*ast.String).Value)
default:
buf.WriteString(c.extractText(ch))
}
}
return buf.String()
}
// extractText recursively collects the text content of all descendant nodes.
func (c *converter) extractText(n ast.Node) string {
var buf bytes.Buffer

View File

@@ -644,6 +644,55 @@ func TestParseMarkdown_MarkdownTableWithInlineMarks(t *testing.T) {
assert.Equal(t, MarkEm, p.Content[2].Marks[0].Type)
}
func TestParseMarkdown_MarkdownTableCellWithBlockList(t *testing.T) {
t.Parallel()
md := "| Header |\n| --- |\n| <ul><li>one</li><li>two</li></ul> |\n"
doc, err := ParseMarkdown(md)
require.NoError(t, err)
require.Len(t, doc.Content, 1)
assert.Equal(t, NodeTable, doc.Content[0].Type)
dataRow := doc.Content[0].Content[1]
assert.Equal(t, NodeTableRow, dataRow.Type)
cell := dataRow.Content[0]
assert.Equal(t, NodeTableCell, cell.Type)
require.Len(t, cell.Content, 1)
assert.Equal(t, NodeBulletList, cell.Content[0].Type)
require.Len(t, cell.Content[0].Content, 2)
assert.Equal(t, NodeListItem, cell.Content[0].Content[0].Type)
assert.Equal(t, NodeListItem, cell.Content[0].Content[1].Type)
li1Para := cell.Content[0].Content[0].Content[0]
require.Equal(t, NodeParagraph, li1Para.Type)
assert.Equal(t, "one", *li1Para.Content[0].Text)
li2Para := cell.Content[0].Content[1].Content[0]
require.Equal(t, NodeParagraph, li2Para.Type)
assert.Equal(t, "two", *li2Para.Content[0].Text)
}
func TestParseMarkdown_MarkdownTableCellWithOrderedList(t *testing.T) {
t.Parallel()
md := "| Header |\n| --- |\n| <ol><li>first</li><li>second</li></ol> |\n"
doc, err := ParseMarkdown(md)
require.NoError(t, err)
require.Len(t, doc.Content, 1)
assert.Equal(t, NodeTable, doc.Content[0].Type)
dataRow := doc.Content[0].Content[1]
cell := dataRow.Content[0]
assert.Equal(t, NodeTableCell, cell.Type)
require.Len(t, cell.Content, 1)
assert.Equal(t, NodeOrderedList, cell.Content[0].Type)
require.Len(t, cell.Content[0].Content, 2)
assert.Equal(t, NodeListItem, cell.Content[0].Content[0].Type)
assert.Equal(t, NodeListItem, cell.Content[0].Content[1].Type)
}
func TestParseMarkdown_BlockHTMLScriptRemovedKeepsSafeContent(t *testing.T) {
t.Parallel()