1package parser
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/yuin/goldmark/ast"
8 "github.com/yuin/goldmark/text"
9 "github.com/yuin/goldmark/util"
10)
11
12var linkLabelStateKey = NewContextKey()
13
14type linkLabelState struct {
15 ast.BaseInline
16
17 Segment text.Segment
18
19 IsImage bool
20
21 Prev *linkLabelState
22
23 Next *linkLabelState
24
25 First *linkLabelState
26
27 Last *linkLabelState
28}
29
30func newLinkLabelState(segment text.Segment, isImage bool) *linkLabelState {
31 return &linkLabelState{
32 Segment: segment,
33 IsImage: isImage,
34 }
35}
36
37func (s *linkLabelState) Text(source []byte) []byte {
38 return s.Segment.Value(source)
39}
40
41func (s *linkLabelState) Dump(source []byte, level int) {
42 fmt.Printf("%slinkLabelState: \"%s\"\n", strings.Repeat(" ", level), s.Text(source))
43}
44
45var kindLinkLabelState = ast.NewNodeKind("LinkLabelState")
46
47func (s *linkLabelState) Kind() ast.NodeKind {
48 return kindLinkLabelState
49}
50
51func linkLabelStateLength(v *linkLabelState) int {
52 if v == nil || v.Last == nil || v.First == nil {
53 return 0
54 }
55 return v.Last.Segment.Stop - v.First.Segment.Start
56}
57
58func pushLinkLabelState(pc Context, v *linkLabelState) {
59 tlist := pc.Get(linkLabelStateKey)
60 var list *linkLabelState
61 if tlist == nil {
62 list = v
63 v.First = v
64 v.Last = v
65 pc.Set(linkLabelStateKey, list)
66 } else {
67 list = tlist.(*linkLabelState)
68 l := list.Last
69 list.Last = v
70 l.Next = v
71 v.Prev = l
72 }
73}
74
75func removeLinkLabelState(pc Context, d *linkLabelState) {
76 tlist := pc.Get(linkLabelStateKey)
77 var list *linkLabelState
78 if tlist == nil {
79 return
80 }
81 list = tlist.(*linkLabelState)
82
83 if d.Prev == nil {
84 list = d.Next
85 if list != nil {
86 list.First = d
87 list.Last = d.Last
88 list.Prev = nil
89 pc.Set(linkLabelStateKey, list)
90 } else {
91 pc.Set(linkLabelStateKey, nil)
92 }
93 } else {
94 d.Prev.Next = d.Next
95 if d.Next != nil {
96 d.Next.Prev = d.Prev
97 }
98 }
99 if list != nil && d.Next == nil {
100 list.Last = d.Prev
101 }
102 d.Next = nil
103 d.Prev = nil
104 d.First = nil
105 d.Last = nil
106}
107
108type linkParser struct {
109}
110
111var defaultLinkParser = &linkParser{}
112
113// NewLinkParser return a new InlineParser that parses links.
114func NewLinkParser() InlineParser {
115 return defaultLinkParser
116}
117
118func (s *linkParser) Trigger() []byte {
119 return []byte{'!', '[', ']'}
120}
121
122var linkBottom = NewContextKey()
123
124func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.Node {
125 line, segment := block.PeekLine()
126 if line[0] == '!' {
127 if len(line) > 1 && line[1] == '[' {
128 block.Advance(1)
129 pushLinkBottom(pc)
130 return processLinkLabelOpen(block, segment.Start+1, true, pc)
131 }
132 return nil
133 }
134 if line[0] == '[' {
135 pushLinkBottom(pc)
136 return processLinkLabelOpen(block, segment.Start, false, pc)
137 }
138
139 // line[0] == ']'
140 tlist := pc.Get(linkLabelStateKey)
141 if tlist == nil {
142 return nil
143 }
144 last := tlist.(*linkLabelState).Last
145 if last == nil {
146 _ = popLinkBottom(pc)
147 return nil
148 }
149 block.Advance(1)
150 removeLinkLabelState(pc, last)
151 // CommonMark spec says:
152 // > A link label can have at most 999 characters inside the square brackets.
153 if linkLabelStateLength(tlist.(*linkLabelState)) > 998 {
154 ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
155 _ = popLinkBottom(pc)
156 return nil
157 }
158
159 if !last.IsImage && s.containsLink(last) { // a link in a link text is not allowed
160 ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
161 _ = popLinkBottom(pc)
162 return nil
163 }
164
165 c := block.Peek()
166 l, pos := block.Position()
167 var link *ast.Link
168 var hasValue bool
169 if c == '(' { // normal link
170 link = s.parseLink(parent, last, block, pc)
171 } else if c == '[' { // reference link
172 link, hasValue = s.parseReferenceLink(parent, last, block, pc)
173 if link == nil && hasValue {
174 ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
175 _ = popLinkBottom(pc)
176 return nil
177 }
178 }
179
180 if link == nil {
181 // maybe shortcut reference link
182 block.SetPosition(l, pos)
183 ssegment := text.NewSegment(last.Segment.Stop, segment.Start)
184 maybeReference := block.Value(ssegment)
185 // CommonMark spec says:
186 // > A link label can have at most 999 characters inside the square brackets.
187 if len(maybeReference) > 999 {
188 ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
189 _ = popLinkBottom(pc)
190 return nil
191 }
192
193 ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
194 if !ok {
195 ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
196 _ = popLinkBottom(pc)
197 return nil
198 }
199 link = ast.NewLink()
200 s.processLinkLabel(parent, link, last, pc)
201 link.Title = ref.Title()
202 link.Destination = ref.Destination()
203 }
204 if last.IsImage {
205 last.Parent().RemoveChild(last.Parent(), last)
206 return ast.NewImage(link)
207 }
208 last.Parent().RemoveChild(last.Parent(), last)
209 return link
210}
211
212func (s *linkParser) containsLink(n ast.Node) bool {
213 if n == nil {
214 return false
215 }
216 for c := n; c != nil; c = c.NextSibling() {
217 if _, ok := c.(*ast.Link); ok {
218 return true
219 }
220 if s.containsLink(c.FirstChild()) {
221 return true
222 }
223 }
224 return false
225}
226
227func processLinkLabelOpen(block text.Reader, pos int, isImage bool, pc Context) *linkLabelState {
228 start := pos
229 if isImage {
230 start--
231 }
232 state := newLinkLabelState(text.NewSegment(start, pos+1), isImage)
233 pushLinkLabelState(pc, state)
234 block.Advance(1)
235 return state
236}
237
238func (s *linkParser) processLinkLabel(parent ast.Node, link *ast.Link, last *linkLabelState, pc Context) {
239 bottom := popLinkBottom(pc)
240 ProcessDelimiters(bottom, pc)
241 for c := last.NextSibling(); c != nil; {
242 next := c.NextSibling()
243 parent.RemoveChild(parent, c)
244 link.AppendChild(link, c)
245 c = next
246 }
247}
248
249var linkFindClosureOptions text.FindClosureOptions = text.FindClosureOptions{
250 Nesting: false,
251 Newline: true,
252 Advance: true,
253}
254
255func (s *linkParser) parseReferenceLink(parent ast.Node, last *linkLabelState,
256 block text.Reader, pc Context) (*ast.Link, bool) {
257 _, orgpos := block.Position()
258 block.Advance(1) // skip '['
259 segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
260 if !found {
261 return nil, false
262 }
263
264 var maybeReference []byte
265 if segments.Len() == 1 { // avoid allocate a new byte slice
266 maybeReference = block.Value(segments.At(0))
267 } else {
268 maybeReference = []byte{}
269 for i := 0; i < segments.Len(); i++ {
270 s := segments.At(i)
271 maybeReference = append(maybeReference, block.Value(s)...)
272 }
273 }
274 if util.IsBlank(maybeReference) { // collapsed reference link
275 s := text.NewSegment(last.Segment.Stop, orgpos.Start-1)
276 maybeReference = block.Value(s)
277 }
278 // CommonMark spec says:
279 // > A link label can have at most 999 characters inside the square brackets.
280 if len(maybeReference) > 999 {
281 return nil, true
282 }
283
284 ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
285 if !ok {
286 return nil, true
287 }
288
289 link := ast.NewLink()
290 s.processLinkLabel(parent, link, last, pc)
291 link.Title = ref.Title()
292 link.Destination = ref.Destination()
293 return link, true
294}
295
296func (s *linkParser) parseLink(parent ast.Node, last *linkLabelState, block text.Reader, pc Context) *ast.Link {
297 block.Advance(1) // skip '('
298 block.SkipSpaces()
299 var title []byte
300 var destination []byte
301 var ok bool
302 if block.Peek() == ')' { // empty link like '[link]()'
303 block.Advance(1)
304 } else {
305 destination, ok = parseLinkDestination(block)
306 if !ok {
307 return nil
308 }
309 block.SkipSpaces()
310 if block.Peek() == ')' {
311 block.Advance(1)
312 } else {
313 title, ok = parseLinkTitle(block)
314 if !ok {
315 return nil
316 }
317 block.SkipSpaces()
318 if block.Peek() == ')' {
319 block.Advance(1)
320 } else {
321 return nil
322 }
323 }
324 }
325
326 link := ast.NewLink()
327 s.processLinkLabel(parent, link, last, pc)
328 link.Destination = destination
329 link.Title = title
330 return link
331}
332
333func parseLinkDestination(block text.Reader) ([]byte, bool) {
334 block.SkipSpaces()
335 line, _ := block.PeekLine()
336 if block.Peek() == '<' {
337 i := 1
338 for i < len(line) {
339 c := line[i]
340 if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
341 i += 2
342 continue
343 } else if c == '>' {
344 block.Advance(i + 1)
345 return line[1:i], true
346 }
347 i++
348 }
349 return nil, false
350 }
351 opened := 0
352 i := 0
353 for i < len(line) {
354 c := line[i]
355 if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
356 i += 2
357 continue
358 } else if c == '(' {
359 opened++
360 } else if c == ')' {
361 opened--
362 if opened < 0 {
363 break
364 }
365 } else if util.IsSpace(c) {
366 break
367 }
368 i++
369 }
370 block.Advance(i)
371 return line[:i], len(line[:i]) != 0
372}
373
374func parseLinkTitle(block text.Reader) ([]byte, bool) {
375 block.SkipSpaces()
376 opener := block.Peek()
377 if opener != '"' && opener != '\'' && opener != '(' {
378 return nil, false
379 }
380 closer := opener
381 if opener == '(' {
382 closer = ')'
383 }
384 block.Advance(1)
385 segments, found := block.FindClosure(opener, closer, linkFindClosureOptions)
386 if found {
387 if segments.Len() == 1 {
388 return block.Value(segments.At(0)), true
389 }
390 var title []byte
391 for i := 0; i < segments.Len(); i++ {
392 s := segments.At(i)
393 title = append(title, block.Value(s)...)
394 }
395 return title, true
396 }
397 return nil, false
398}
399
400func pushLinkBottom(pc Context) {
401 bottoms := pc.Get(linkBottom)
402 b := pc.LastDelimiter()
403 if bottoms == nil {
404 pc.Set(linkBottom, b)
405 return
406 }
407 if s, ok := bottoms.([]ast.Node); ok {
408 pc.Set(linkBottom, append(s, b))
409 return
410 }
411 pc.Set(linkBottom, []ast.Node{bottoms.(ast.Node), b})
412}
413
414func popLinkBottom(pc Context) ast.Node {
415 bottoms := pc.Get(linkBottom)
416 if bottoms == nil {
417 return nil
418 }
419 if v, ok := bottoms.(ast.Node); ok {
420 pc.Set(linkBottom, nil)
421 return v
422 }
423 s := bottoms.([]ast.Node)
424 v := s[len(s)-1]
425 n := s[0 : len(s)-1]
426 switch len(n) {
427 case 0:
428 pc.Set(linkBottom, nil)
429 case 1:
430 pc.Set(linkBottom, n[0])
431 default:
432 pc.Set(linkBottom, s[0:len(s)-1])
433 }
434 return v
435}
436
437func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
438 pc.Set(linkBottom, nil)
439 tlist := pc.Get(linkLabelStateKey)
440 if tlist == nil {
441 return
442 }
443 for s := tlist.(*linkLabelState); s != nil; {
444 next := s.Next
445 removeLinkLabelState(pc, s)
446 s.Parent().ReplaceChild(s.Parent(), s, ast.NewTextSegment(s.Segment))
447 s = next
448 }
449}