...

Source file src/github.com/yuin/goldmark/parser/link.go

Documentation: github.com/yuin/goldmark/parser

     1  package parser
     2  
     3  import (
     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  
    12  var linkLabelStateKey = NewContextKey()
    13  
    14  type 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  
    30  func newLinkLabelState(segment text.Segment, isImage bool) *linkLabelState {
    31  	return &linkLabelState{
    32  		Segment: segment,
    33  		IsImage: isImage,
    34  	}
    35  }
    36  
    37  func (s *linkLabelState) Text(source []byte) []byte {
    38  	return s.Segment.Value(source)
    39  }
    40  
    41  func (s *linkLabelState) Dump(source []byte, level int) {
    42  	fmt.Printf("%slinkLabelState: \"%s\"\n", strings.Repeat("    ", level), s.Text(source))
    43  }
    44  
    45  var kindLinkLabelState = ast.NewNodeKind("LinkLabelState")
    46  
    47  func (s *linkLabelState) Kind() ast.NodeKind {
    48  	return kindLinkLabelState
    49  }
    50  
    51  func 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  
    58  func 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  
    75  func 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  
   108  type linkParser struct {
   109  }
   110  
   111  var defaultLinkParser = &linkParser{}
   112  
   113  // NewLinkParser return a new InlineParser that parses links.
   114  func NewLinkParser() InlineParser {
   115  	return defaultLinkParser
   116  }
   117  
   118  func (s *linkParser) Trigger() []byte {
   119  	return []byte{'!', '[', ']'}
   120  }
   121  
   122  var linkBottom = NewContextKey()
   123  
   124  func (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  
   206  func (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  
   221  func 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  
   232  func (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  
   247  var linkFindClosureOptions text.FindClosureOptions = text.FindClosureOptions{
   248  	Nesting: false,
   249  	Newline: true,
   250  	Advance: true,
   251  }
   252  
   253  func (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  
   293  func (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  
   330  func 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  
   371  func 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  
   397  func (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  }
   410  

View as plain text