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 }