link.go

  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}