1package parser
2
3import (
4 "github.com/yuin/goldmark/ast"
5 "github.com/yuin/goldmark/text"
6 "github.com/yuin/goldmark/util"
7)
8
9type linkReferenceParagraphTransformer struct {
10}
11
12// LinkReferenceParagraphTransformer is a ParagraphTransformer implementation
13// that parses and extracts link reference from paragraphs.
14var LinkReferenceParagraphTransformer = &linkReferenceParagraphTransformer{}
15
16func (p *linkReferenceParagraphTransformer) Transform(node *ast.Paragraph, reader text.Reader, pc Context) {
17 lines := node.Lines()
18 block := text.NewBlockReader(reader.Source(), lines)
19 removes := [][2]int{}
20 for {
21 ref, start, end := parseLinkReferenceDefinition(block, pc)
22 if start > -1 {
23 if start == 0 {
24 ref.SetBlankPreviousLines(node.HasBlankPreviousLines())
25 }
26 node.Parent().InsertBefore(node.Parent(), node, ref)
27 for i := start + 1; i < end; i++ {
28 ref.Lines().Append(lines.At(i))
29 }
30 seg := ref.Lines().At(ref.Lines().Len() - 1)
31 ref.Lines().Set(ref.Lines().Len()-1, seg.TrimRightSpace(reader.Source()))
32 if start == end {
33 end++
34 }
35 removes = append(removes, [2]int{start, end})
36 continue
37 }
38 break
39 }
40
41 offset := 0
42 for _, remove := range removes {
43 if lines.Len() == 0 {
44 break
45 }
46 s := lines.Sliced(remove[1]-offset, lines.Len())
47 lines.SetSliced(0, remove[0]-offset)
48 lines.AppendAll(s)
49 offset = remove[1]
50 }
51
52 if lines.Len() == 0 {
53 node.Parent().RemoveChild(node.Parent(), node)
54 return
55 }
56
57 node.SetLines(lines)
58}
59
60func parseLinkReferenceDefinition(block text.Reader, pc Context) (ast.Node, int, int) {
61 block.SkipSpaces()
62 line, _ := block.PeekLine()
63 if line == nil {
64 return nil, -1, -1
65 }
66 startLine, _ := block.Position()
67 width, pos := util.IndentWidth(line, 0)
68 if width > 3 {
69 return nil, -1, -1
70 }
71 if width != 0 {
72 pos++
73 }
74 if line[pos] != '[' {
75 return nil, -1, -1
76 }
77 _, startPos := block.Position()
78 block.Advance(pos + 1)
79 segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
80 if !found {
81 return nil, -1, -1
82 }
83 var label []byte
84 if segments.Len() == 1 {
85 label = block.Value(segments.At(0))
86 } else {
87 for i := range segments.Len() {
88 s := segments.At(i)
89 label = append(label, block.Value(s)...)
90 }
91 }
92 if util.IsBlank(label) {
93 return nil, -1, -1
94 }
95 if block.Peek() != ':' {
96 return nil, -1, -1
97 }
98 block.Advance(1)
99 block.SkipSpaces()
100 destination, ok := parseLinkDestination(block)
101 if !ok {
102 return nil, -1, -1
103 }
104 line, _ = block.PeekLine()
105 isNewLine := line == nil || util.IsBlank(line)
106
107 endLine, _ := block.Position()
108 _, spaces, _ := block.SkipSpaces()
109 opener := block.Peek()
110 if opener != '"' && opener != '\'' && opener != '(' {
111 if !isNewLine {
112 return nil, -1, -1
113 }
114 ref := ast.NewLinkReferenceDefinition(label, destination, nil)
115 ref.Lines().Append(startPos)
116 pc.AddReference(newASTReference(ref))
117 return ref, startLine, endLine + 1
118 }
119 if spaces == 0 {
120 return nil, -1, -1
121 }
122 block.Advance(1)
123 closer := opener
124 if opener == '(' {
125 closer = ')'
126 }
127 segments, found = block.FindClosure(opener, closer, linkFindClosureOptions)
128 if !found {
129 if !isNewLine {
130 return nil, -1, -1
131 }
132 ref := ast.NewLinkReferenceDefinition(label, destination, nil)
133 ref.Lines().Append(startPos)
134 pc.AddReference(newASTReference(ref))
135 block.AdvanceLine()
136 return ref, startLine, endLine + 1
137 }
138 var title []byte
139 if segments.Len() == 1 {
140 title = block.Value(segments.At(0))
141 } else {
142 for i := range segments.Len() {
143 s := segments.At(i)
144 title = append(title, block.Value(s)...)
145 }
146 }
147
148 line, _ = block.PeekLine()
149 if line != nil && !util.IsBlank(line) {
150 if !isNewLine {
151 return nil, -1, -1
152 }
153 ref := ast.NewLinkReferenceDefinition(label, destination, title)
154 ref.Lines().Append(startPos)
155 pc.AddReference(newASTReference(ref))
156 return ref, startLine, endLine
157 }
158
159 endLine, _ = block.Position()
160 ref := ast.NewLinkReferenceDefinition(label, destination, title)
161 ref.Lines().Append(startPos)
162 pc.AddReference(newASTReference(ref))
163 return ref, startLine, endLine + 1
164}