Skip to content

Commit c9fa41c

Browse files
feat: refactor italic node to support nested inline elements (#20)
* feat: enhance bold and italic parsers to support underscore syntax * fix: adjust blockquote parser to handle empty content rows * feat: refactor italic node structure to use children for content representation --------- Co-authored-by: Anubhav Singh <[email protected]>
1 parent 97a73c2 commit c9fa41c

File tree

9 files changed

+138
-26
lines changed

9 files changed

+138
-26
lines changed

ast/inline.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,20 @@ type Italic struct {
4848
BaseInline
4949

5050
// Symbol is "*" or "_".
51-
Symbol string
52-
Content string
51+
Symbol string
52+
Children []Node
5353
}
5454

5555
func (*Italic) Type() NodeType {
5656
return ItalicNode
5757
}
5858

5959
func (n *Italic) Restore() string {
60-
return fmt.Sprintf("%s%s%s", n.Symbol, n.Content, n.Symbol)
60+
content := ""
61+
for _, child := range n.Children {
62+
content += child.Restore()
63+
}
64+
return fmt.Sprintf("%s%s%s", n.Symbol, content, n.Symbol)
6165
}
6266

6367
type BoldItalic struct {

parser/blockquote.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func (*BlockquoteParser) Match(tokens []*tokenizer.Token) (ast.Node, int) {
1515
rows := tokenizer.Split(tokens, tokenizer.NewLine)
1616
contentRows := [][]*tokenizer.Token{}
1717
for _, row := range rows {
18-
if len(row) < 3 || row[0].Type != tokenizer.GreaterThan || row[1].Type != tokenizer.Space {
18+
if len(row) < 2 || row[0].Type != tokenizer.GreaterThan || row[1].Type != tokenizer.Space {
1919
break
2020
}
2121
contentRows = append(contentRows, row)
@@ -26,17 +26,25 @@ func (*BlockquoteParser) Match(tokens []*tokenizer.Token) (ast.Node, int) {
2626

2727
children := []ast.Node{}
2828
size := 0
29+
2930
for index, row := range contentRows {
3031
contentTokens := row[2:]
31-
nodes, err := ParseBlockWithParsers(contentTokens, []BlockParser{NewBlockquoteParser(), NewParagraphParser()})
32-
if err != nil {
33-
return nil, 0
34-
}
35-
if len(nodes) != 1 {
36-
return nil, 0
32+
var node ast.Node
33+
if len(contentTokens) == 0 {
34+
node = &ast.Paragraph{
35+
Children: []ast.Node{&ast.Text{Content: " "}},
36+
}
37+
} else {
38+
nodes, err := ParseBlockWithParsers(contentTokens, []BlockParser{NewBlockquoteParser(), NewParagraphParser()})
39+
if err != nil {
40+
return nil, 0
41+
}
42+
if len(nodes) != 1 {
43+
return nil, 0
44+
}
45+
node = nodes[0]
3746
}
38-
39-
children = append(children, nodes[0])
47+
children = append(children, node)
4048
size += len(row)
4149
if index != len(contentRows)-1 {
4250
size++ // NewLine.

parser/bold.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func (*BoldParser) Match(tokens []*tokenizer.Token) (ast.Node, int) {
2222
return nil, 0
2323
}
2424
prefixTokenType := prefixTokens[0].Type
25-
if prefixTokenType != tokenizer.Asterisk {
25+
if prefixTokenType != tokenizer.Asterisk && prefixTokenType != tokenizer.Underscore {
2626
return nil, 0
2727
}
2828

parser/italic.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func (*ItalicParser) Match(tokens []*tokenizer.Token) (ast.Node, int) {
2020
}
2121

2222
prefixTokens := matchedTokens[:1]
23-
if prefixTokens[0].Type != tokenizer.Asterisk {
23+
if prefixTokens[0].Type != tokenizer.Asterisk && prefixTokens[0].Type != tokenizer.Underscore {
2424
return nil, 0
2525
}
2626
prefixTokenType := prefixTokens[0].Type
@@ -37,8 +37,13 @@ func (*ItalicParser) Match(tokens []*tokenizer.Token) (ast.Node, int) {
3737
return nil, 0
3838
}
3939

40+
children, err := ParseInlineWithParsers(contentTokens, []InlineParser{NewLinkParser(), NewTextParser()})
41+
if err != nil || len(children) == 0 {
42+
return nil, 0
43+
}
44+
4045
return &ast.Italic{
41-
Symbol: prefixTokenType,
42-
Content: tokenizer.Stringify(contentTokens),
46+
Symbol: prefixTokenType,
47+
Children: children,
4348
}, len(contentTokens) + 2
4449
}

parser/tests/bold_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,36 @@ func TestBoldParser(t *testing.T) {
5252
text: "* * Hello **",
5353
node: nil,
5454
},
55+
{
56+
text: "__Hello__",
57+
node: &ast.Bold{
58+
Symbol: "_",
59+
Children: []ast.Node{
60+
&ast.Text{
61+
Content: "Hello",
62+
},
63+
},
64+
},
65+
},
66+
{
67+
text: "__ Hello __",
68+
node: &ast.Bold{
69+
Symbol: "_",
70+
Children: []ast.Node{
71+
&ast.Text{
72+
Content: " Hello ",
73+
},
74+
},
75+
},
76+
},
77+
{
78+
text: "__ Hello _ _",
79+
node: nil,
80+
},
81+
{
82+
text: "_ _ Hello __",
83+
node: nil,
84+
},
5585
}
5686

5787
for _, test := range tests {

parser/tests/italic_test.go

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,83 @@ func TestItalicParser(t *testing.T) {
2121
{
2222
text: "*Hello*",
2323
node: &ast.Italic{
24-
Symbol: "*",
25-
Content: "Hello",
24+
Symbol: "*",
25+
Children: []ast.Node{
26+
&ast.Text{
27+
Content: "Hello",
28+
},
29+
},
2630
},
2731
},
2832
{
2933
text: "* Hello *",
3034
node: &ast.Italic{
31-
Symbol: "*",
32-
Content: " Hello ",
35+
Symbol: "*",
36+
Children: []ast.Node{
37+
&ast.Text{
38+
Content: " Hello ",
39+
},
40+
},
3341
},
3442
},
3543
{
3644
text: "*1* Hello * *",
3745
node: &ast.Italic{
38-
Symbol: "*",
39-
Content: "1",
46+
Symbol: "*",
47+
Children: []ast.Node{
48+
&ast.Text{
49+
Content: "1",
50+
},
51+
},
52+
},
53+
},
54+
{
55+
text: "_Hello_",
56+
node: &ast.Italic{
57+
Symbol: "_",
58+
Children: []ast.Node{
59+
&ast.Text{
60+
Content: "Hello",
61+
},
62+
},
63+
},
64+
},
65+
{
66+
text: "_ Hello _",
67+
node: &ast.Italic{
68+
Symbol: "_",
69+
Children: []ast.Node{
70+
&ast.Text{
71+
Content: " Hello ",
72+
},
73+
},
74+
},
75+
},
76+
{
77+
text: "_1_ Hello _ _",
78+
node: &ast.Italic{
79+
Symbol: "_",
80+
Children: []ast.Node{
81+
&ast.Text{
82+
Content: "1",
83+
},
84+
},
85+
},
86+
},
87+
{
88+
text: "*[Hello](https://example.com)*",
89+
node: &ast.Italic{
90+
Symbol: "*",
91+
Children: []ast.Node{
92+
&ast.Link{
93+
Content: []ast.Node{
94+
&ast.Text{
95+
Content: "Hello",
96+
},
97+
},
98+
URL: "https://example.com",
99+
},
100+
},
40101
},
41102
},
42103
}

parser/tests/ordered_list_item_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,12 @@ func TestOrderedListItemParser(t *testing.T) {
5555
Content: "Hello ",
5656
},
5757
&ast.Italic{
58-
Symbol: "*",
59-
Content: "World",
58+
Symbol: "*",
59+
Children: []ast.Node{
60+
&ast.Text{
61+
Content: "World",
62+
},
63+
},
6064
},
6165
},
6266
},

renderer/html/html.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func (r *HTMLRenderer) renderBold(node *ast.Bold) {
249249

250250
func (r *HTMLRenderer) renderItalic(node *ast.Italic) {
251251
r.output.WriteString("<em>")
252-
r.output.WriteString(node.Content)
252+
r.RenderNodes(node.Children)
253253
r.output.WriteString("</em>")
254254
}
255255

renderer/string/string.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func (r *StringRenderer) renderBold(node *ast.Bold) {
197197
}
198198

199199
func (r *StringRenderer) renderItalic(node *ast.Italic) {
200-
r.output.WriteString(node.Content)
200+
r.RenderNodes(node.Children)
201201
}
202202

203203
func (r *StringRenderer) renderBoldItalic(node *ast.BoldItalic) {

0 commit comments

Comments
 (0)