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