...

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,
   254  	block text.Reader, pc Context) (*ast.Link, bool) {
   255  	_, orgpos := block.Position()
   256  	block.Advance(1) // skip '['
   257  	segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
   258  	if !found {
   259  		return nil, false
   260  	}
   261  
   262  	var maybeReference []byte
   263  	if segments.Len() == 1 { // avoid allocate a new byte slice
   264  		maybeReference = block.Value(segments.At(0))
   265  	} else {
   266  		maybeReference = []byte{}
   267  		for i := 0; i < segments.Len(); i++ {
   268  			s := segments.At(i)
   269  			maybeReference = append(maybeReference, block.Value(s)...)
   270  		}
   271  	}
   272  	if util.IsBlank(maybeReference) { // collapsed reference link
   273  		s := text.NewSegment(last.Segment.Stop, orgpos.Start-1)
   274  		maybeReference = block.Value(s)
   275  	}
   276  	// CommonMark spec says:
   277  	//  > A link label can have at most 999 characters inside the square brackets.
   278  	if len(maybeReference) > 999 {
   279  		return nil, true
   280  	}
   281  
   282  	ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
   283  	if !ok {
   284  		return nil, true
   285  	}
   286  
   287  	link := ast.NewLink()
   288  	s.processLinkLabel(parent, link, last, pc)
   289  	link.Title = ref.Title()
   290  	link.Destination = ref.Destination()
   291  	return link, true
   292  }
   293  
   294  func (s *linkParser) parseLink(parent ast.Node, last *linkLabelState, block text.Reader, pc Context) *ast.Link {
   295  	block.Advance(1) // skip '('
   296  	block.SkipSpaces()
   297  	var title []byte
   298  	var destination []byte
   299  	var ok bool
   300  	if block.Peek() == ')' { // empty link like '[link]()'
   301  		block.Advance(1)
   302  	} else {
   303  		destination, ok = parseLinkDestination(block)
   304  		if !ok {
   305  			return nil
   306  		}
   307  		block.SkipSpaces()
   308  		if block.Peek() == ')' {
   309  			block.Advance(1)
   310  		} else {
   311  			title, ok = parseLinkTitle(block)
   312  			if !ok {
   313  				return nil
   314  			}
   315  			block.SkipSpaces()
   316  			if block.Peek() == ')' {
   317  				block.Advance(1)
   318  			} else {
   319  				return nil
   320  			}
   321  		}
   322  	}
   323  
   324  	link := ast.NewLink()
   325  	s.processLinkLabel(parent, link, last, pc)
   326  	link.Destination = destination
   327  	link.Title = title
   328  	return link
   329  }
   330  
   331  func parseLinkDestination(block text.Reader) ([]byte, bool) {
   332  	block.SkipSpaces()
   333  	line, _ := block.PeekLine()
   334  	if block.Peek() == '<' {
   335  		i := 1
   336  		for i < len(line) {
   337  			c := line[i]
   338  			if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
   339  				i += 2
   340  				continue
   341  			} else if c == '>' {
   342  				block.Advance(i + 1)
   343  				return line[1:i], true
   344  			}
   345  			i++
   346  		}
   347  		return nil, false
   348  	}
   349  	opened := 0
   350  	i := 0
   351  	for i < len(line) {
   352  		c := line[i]
   353  		if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
   354  			i += 2
   355  			continue
   356  		} else if c == '(' {
   357  			opened++
   358  		} else if c == ')' {
   359  			opened--
   360  			if opened < 0 {
   361  				break
   362  			}
   363  		} else if util.IsSpace(c) {
   364  			break
   365  		}
   366  		i++
   367  	}
   368  	block.Advance(i)
   369  	return line[:i], len(line[:i]) != 0
   370  }
   371  
   372  func parseLinkTitle(block text.Reader) ([]byte, bool) {
   373  	block.SkipSpaces()
   374  	opener := block.Peek()
   375  	if opener != '"' && opener != '\'' && opener != '(' {
   376  		return nil, false
   377  	}
   378  	closer := opener
   379  	if opener == '(' {
   380  		closer = ')'
   381  	}
   382  	block.Advance(1)
   383  	segments, found := block.FindClosure(opener, closer, linkFindClosureOptions)
   384  	if found {
   385  		if segments.Len() == 1 {
   386  			return block.Value(segments.At(0)), true
   387  		}
   388  		var title []byte
   389  		for i := 0; i < segments.Len(); i++ {
   390  			s := segments.At(i)
   391  			title = append(title, block.Value(s)...)
   392  		}
   393  		return title, true
   394  	}
   395  	return nil, false
   396  }
   397  
   398  func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
   399  	pc.Set(linkBottom, nil)
   400  	tlist := pc.Get(linkLabelStateKey)
   401  	if tlist == nil {
   402  		return
   403  	}
   404  	for s := tlist.(*linkLabelState); s != nil; {
   405  		next := s.Next
   406  		removeLinkLabelState(pc, s)
   407  		s.Parent().ReplaceChild(s.Parent(), s, ast.NewTextSegment(s.Segment))
   408  		s = next
   409  	}
   410  }
   411  

View as plain text