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, block text.Reader, pc Context) (*ast.Link, bool) {
254 _, orgpos := block.Position()
255 block.Advance(1)
256 segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
257 if !found {
258 return nil, false
259 }
260
261 var maybeReference []byte
262 if segments.Len() == 1 {
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) {
272 s := text.NewSegment(last.Segment.Stop, orgpos.Start-1)
273 maybeReference = block.Value(s)
274 }
275
276
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)
295 block.SkipSpaces()
296 var title []byte
297 var destination []byte
298 var ok bool
299 if block.Peek() == ')' {
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