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}