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
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
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
151
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) {
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 == '(' {
167 link = s.parseLink(parent, last, block, pc)
168 } else if c == '[' {
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
178 block.SetPosition(l, pos)
179 ssegment := text.NewSegment(last.Segment.Stop, segment.Start)
180 maybeReference := block.Value(ssegment)
181
182
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)
257 segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
258 if !found {
259 return nil, false
260 }
261
262 var maybeReference []byte
263 if segments.Len() == 1 {
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) {
273 s := text.NewSegment(last.Segment.Stop, orgpos.Start-1)
274 maybeReference = block.Value(s)
275 }
276
277
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)
296 block.SkipSpaces()
297 var title []byte
298 var destination []byte
299 var ok bool
300 if block.Peek() == ')' {
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