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 switch c {
170 case '(':
171 link = s.parseLink(parent, last, block, pc)
172 case '[':
173 link, hasValue = s.parseReferenceLink(parent, last, block, pc)
174 if link == nil && hasValue {
175 ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
176 _ = popLinkBottom(pc)
177 return nil
178 }
179 }
180
181 if link == nil {
182 // maybe shortcut reference link
183 block.SetPosition(l, pos)
184 ssegment := text.NewSegment(last.Segment.Stop, segment.Start)
185 maybeReference := block.Value(ssegment)
186 // CommonMark spec says:
187 // > A link label can have at most 999 characters inside the square brackets.
188 if len(maybeReference) > 999 {
189 ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
190 _ = popLinkBottom(pc)
191 return nil
192 }
193
194 ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
195 if !ok {
196 ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
197 _ = popLinkBottom(pc)
198 return nil
199 }
200 link = ast.NewLink()
201 s.processLinkLabel(parent, link, last, pc)
202 link.Title = ref.Title()
203 link.Destination = ref.Destination()
204 link.Reference = ast.NewReferenceLink(ast.ReferenceLinkShortcut, maybeReference)
205 }
206 var n ast.Node
207 if last.IsImage {
208 last.Parent().RemoveChild(last.Parent(), last)
209 n = ast.NewImage(link)
210 } else {
211 last.Parent().RemoveChild(last.Parent(), last)
212 n = link
213 }
214 n.(interface{ SetPos(int) }).SetPos(last.Segment.Start)
215 return n
216}
217
218func (s *linkParser) containsLink(n ast.Node) bool {
219 if n == nil {
220 return false
221 }
222 for c := n; c != nil; c = c.NextSibling() {
223 if _, ok := c.(*ast.Link); ok {
224 return true
225 }
226 if s.containsLink(c.FirstChild()) {
227 return true
228 }
229 }
230 return false
231}
232
233func processLinkLabelOpen(block text.Reader, pos int, isImage bool, pc Context) *linkLabelState {
234 start := pos
235 if isImage {
236 start--
237 }
238 state := newLinkLabelState(text.NewSegment(start, pos+1), isImage)
239 pushLinkLabelState(pc, state)
240 block.Advance(1)
241 return state
242}
243
244func (s *linkParser) processLinkLabel(parent ast.Node, link *ast.Link, last *linkLabelState, pc Context) {
245 bottom := popLinkBottom(pc)
246 ProcessDelimiters(bottom, pc)
247 for c := last.NextSibling(); c != nil; {
248 next := c.NextSibling()
249 parent.RemoveChild(parent, c)
250 link.AppendChild(link, c)
251 c = next
252 }
253}
254
255var linkFindClosureOptions text.FindClosureOptions = text.FindClosureOptions{
256 Nesting: false,
257 Newline: true,
258 Advance: true,
259}
260
261func (s *linkParser) parseReferenceLink(parent ast.Node, last *linkLabelState,
262 block text.Reader, pc Context) (*ast.Link, bool) {
263 _, orgpos := block.Position()
264 block.Advance(1) // skip '['
265 segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
266 if !found {
267 return nil, false
268 }
269
270 var maybeReference []byte
271 refType := ast.ReferenceLinkFull
272 if segments.Len() == 1 { // avoid allocate a new byte slice
273 maybeReference = block.Value(segments.At(0))
274 } else {
275 maybeReference = []byte{}
276 for i := range segments.Len() {
277 s := segments.At(i)
278 maybeReference = append(maybeReference, block.Value(s)...)
279 }
280 }
281 if util.IsBlank(maybeReference) { // collapsed reference link
282 s := text.NewSegment(last.Segment.Stop, orgpos.Start-1)
283 maybeReference = block.Value(s)
284 refType = ast.ReferenceLinkCollapsed
285 }
286 // CommonMark spec says:
287 // > A link label can have at most 999 characters inside the square brackets.
288 if len(maybeReference) > 999 {
289 return nil, true
290 }
291
292 ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
293 if !ok {
294 return nil, true
295 }
296
297 link := ast.NewLink()
298 s.processLinkLabel(parent, link, last, pc)
299 link.Title = ref.Title()
300 link.Destination = ref.Destination()
301 link.Reference = ast.NewReferenceLink(refType, maybeReference)
302 return link, true
303}
304
305func (s *linkParser) parseLink(parent ast.Node, last *linkLabelState, block text.Reader, pc Context) *ast.Link {
306 block.Advance(1) // skip '('
307 block.SkipSpaces()
308 var title []byte
309 var destination []byte
310 var ok bool
311 if block.Peek() == ')' { // empty link like '[link]()'
312 block.Advance(1)
313 } else {
314 destination, ok = parseLinkDestination(block)
315 if !ok {
316 return nil
317 }
318 block.SkipSpaces()
319 if block.Peek() == ')' {
320 block.Advance(1)
321 } else {
322 title, ok = parseLinkTitle(block)
323 if !ok {
324 return nil
325 }
326 block.SkipSpaces()
327 if block.Peek() == ')' {
328 block.Advance(1)
329 } else {
330 return nil
331 }
332 }
333 }
334
335 link := ast.NewLink()
336 s.processLinkLabel(parent, link, last, pc)
337 link.Destination = destination
338 link.Title = title
339 return link
340}
341
342func parseLinkDestination(block text.Reader) ([]byte, bool) {
343 block.SkipSpaces()
344 line, _ := block.PeekLine()
345 if block.Peek() == '<' {
346 i := 1
347 for i < len(line) {
348 c := line[i]
349 if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
350 i += 2
351 continue
352 } else if c == '>' {
353 block.Advance(i + 1)
354 return line[1:i], true
355 }
356 i++
357 }
358 return nil, false
359 }
360 opened := 0
361 i := 0
362 for i < len(line) {
363 c := line[i]
364 if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
365 i += 2
366 continue
367 } else if c == '(' {
368 opened++
369 } else if c == ')' {
370 opened--
371 if opened < 0 {
372 break
373 }
374 } else if util.IsSpace(c) {
375 break
376 }
377 i++
378 }
379 block.Advance(i)
380 return line[:i], len(line[:i]) != 0
381}
382
383func parseLinkTitle(block text.Reader) ([]byte, bool) {
384 block.SkipSpaces()
385 opener := block.Peek()
386 if opener != '"' && opener != '\'' && opener != '(' {
387 return nil, false
388 }
389 closer := opener
390 if opener == '(' {
391 closer = ')'
392 }
393 block.Advance(1)
394 segments, found := block.FindClosure(opener, closer, linkFindClosureOptions)
395 if found {
396 if segments.Len() == 1 {
397 return block.Value(segments.At(0)), true
398 }
399 var title []byte
400 for i := range segments.Len() {
401 s := segments.At(i)
402 title = append(title, block.Value(s)...)
403 }
404 return title, true
405 }
406 return nil, false
407}
408
409func pushLinkBottom(pc Context) {
410 bottoms := pc.Get(linkBottom)
411 b := pc.LastDelimiter()
412 if bottoms == nil {
413 pc.Set(linkBottom, b)
414 return
415 }
416 if s, ok := bottoms.([]ast.Node); ok {
417 pc.Set(linkBottom, append(s, b))
418 return
419 }
420 pc.Set(linkBottom, []ast.Node{bottoms.(ast.Node), b})
421}
422
423func popLinkBottom(pc Context) ast.Node {
424 bottoms := pc.Get(linkBottom)
425 if bottoms == nil {
426 return nil
427 }
428 if v, ok := bottoms.(ast.Node); ok {
429 pc.Set(linkBottom, nil)
430 return v
431 }
432 s := bottoms.([]ast.Node)
433 v := s[len(s)-1]
434 n := s[0 : len(s)-1]
435 switch len(n) {
436 case 0:
437 pc.Set(linkBottom, nil)
438 case 1:
439 pc.Set(linkBottom, n[0])
440 default:
441 pc.Set(linkBottom, s[0:len(s)-1])
442 }
443 return v
444}
445
446func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
447 pc.Set(linkBottom, nil)
448 tlist := pc.Get(linkLabelStateKey)
449 if tlist == nil {
450 return
451 }
452 for s := tlist.(*linkLabelState); s != nil; {
453 next := s.Next
454 removeLinkLabelState(pc, s)
455 s.Parent().ReplaceChild(s.Parent(), s, ast.NewTextSegment(s.Segment))
456 s = next
457 }
458}