gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

link.go (9313B)


      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 }