Skip to content

Commit 3b2985d

Browse files
authored
Merge pull request #126 from osteele/fix-66-void-elements
fix: handle HTML void elements in markdown attribute processing
2 parents f079cab + 4078cf8 commit 3b2985d

2 files changed

Lines changed: 51 additions & 4 deletions

File tree

renderers/markdown_attrs.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,34 @@ import (
44
"bytes"
55
"io"
66
"regexp"
7+
"strings"
78

89
"github.com/osteele/gojekyll/utils"
910
"golang.org/x/net/html"
1011
)
1112

13+
// HTML void elements that are self-closing and never have an end tag.
14+
// See https://html.spec.whatwg.org/multipage/syntax.html#void-elements
15+
var htmlVoidElements = map[string]bool{
16+
"area": true, "base": true, "br": true, "col": true,
17+
"embed": true, "hr": true, "img": true, "input": true,
18+
"link": true, "meta": true, "param": true, "source": true,
19+
"track": true, "wbr": true,
20+
}
21+
22+
// isVoidElement returns true if the raw token bytes represent a void element.
23+
func isVoidElement(raw []byte) bool {
24+
s := strings.TrimLeft(string(raw), "< ")
25+
// Extract tag name (up to space, /, or >)
26+
for i, c := range s {
27+
if c == ' ' || c == '/' || c == '>' || c == '\t' || c == '\n' {
28+
s = s[:i]
29+
break
30+
}
31+
}
32+
return htmlVoidElements[strings.ToLower(s)]
33+
}
34+
1235
var markdownAttrRE = regexp.MustCompile(`\s*markdown\s*=[^\s>]*\s*`)
1336

1437
// Used inside markdown=1.
@@ -107,12 +130,12 @@ loop:
107130
if err == io.EOF {
108131
return utils.WrapError(err,
109132
"unexpected EOF while processing markdown attribute. "+
110-
"Common causes: unclosed HTML tags (use <br/> instead of <br>), "+
133+
"Common causes: unclosed HTML tags, "+
111134
"or mismatched opening/closing tags")
112135
}
113136
return err
114137
case html.StartTagToken:
115-
if !notATagRE.Match(z.Raw()) {
138+
if !notATagRE.Match(z.Raw()) && !isVoidElement(z.Raw()) {
116139
depth++
117140
}
118141
case html.EndTagToken:
@@ -179,12 +202,12 @@ loop:
179202
if err == io.EOF {
180203
return utils.WrapError(err,
181204
"unexpected EOF while processing markdown=\"0\" attribute. "+
182-
"Common causes: unclosed HTML tags (use <br/> instead of <br>), "+
205+
"Common causes: unclosed HTML tags, "+
183206
"or mismatched opening/closing tags")
184207
}
185208
return err
186209
case html.StartTagToken:
187-
if !notATagRE.Match(z.Raw()) {
210+
if !notATagRE.Match(z.Raw()) && !isVoidElement(z.Raw()) {
188211
depth++
189212
}
190213
case html.EndTagToken:

renderers/markdown_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,30 @@ func TestDeIndentHTMLBlocks(t *testing.T) {
102102
}
103103
}
104104

105+
func TestRenderMarkdownVoidElements(t *testing.T) {
106+
// Issue #66: <br> tags inside markdown="1" blocks should not cause EOF errors.
107+
// Void elements like <br>, <hr>, <img> don't have end tags, so the depth
108+
// tracker must not increment for them.
109+
110+
// These should not panic or error (the main fix for #66)
111+
result := mustMarkdownString("\n<div markdown=\"1\">\n<br>\n<br>\n</div>\n")
112+
require.Contains(t, result, "<br")
113+
114+
result = mustMarkdownString("\n<div markdown=\"1\">\n<hr>\n</div>\n")
115+
require.Contains(t, result, "<hr")
116+
117+
result = mustMarkdownString("\n<div markdown=\"1\">\n<img src=\"test.png\">\n</div>\n")
118+
require.Contains(t, result, "img")
119+
120+
// Self-closing variants should also work
121+
result = mustMarkdownString("\n<div markdown=\"1\">\n<br/>\n</div>\n")
122+
require.Contains(t, result, "<br")
123+
124+
// markdown="0" with void elements should also not error
125+
result = mustMarkdownString("\n<div markdown=\"0\">\n<br>\n<br>\ntext\n</div>\n")
126+
require.Contains(t, result, "text")
127+
}
128+
105129
func mustMarkdownString(md string) string {
106130
s, err := renderMarkdown([]byte(md))
107131
if err != nil {

0 commit comments

Comments
 (0)