1 package parser
2
3 import (
4 "github.com/yuin/goldmark/ast"
5 "github.com/yuin/goldmark/text"
6 "github.com/yuin/goldmark/util"
7 )
8
9 type linkReferenceParagraphTransformer struct {
10 }
11
12
13
14 var LinkReferenceParagraphTransformer = &linkReferenceParagraphTransformer{}
15
16 func (p *linkReferenceParagraphTransformer) Transform(node *ast.Paragraph, reader text.Reader, pc Context) {
17 lines := node.Lines()
18 block := text.NewBlockReader(reader.Source(), lines)
19 removes := [][2]int{}
20 for {
21 start, end := parseLinkReferenceDefinition(block, pc)
22 if start > -1 {
23 if start == end {
24 end++
25 }
26 removes = append(removes, [2]int{start, end})
27 continue
28 }
29 break
30 }
31
32 offset := 0
33 for _, remove := range removes {
34 if lines.Len() == 0 {
35 break
36 }
37 s := lines.Sliced(remove[1]-offset, lines.Len())
38 lines.SetSliced(0, remove[0]-offset)
39 lines.AppendAll(s)
40 offset = remove[1]
41 }
42
43 if lines.Len() == 0 {
44 t := ast.NewTextBlock()
45 t.SetBlankPreviousLines(node.HasBlankPreviousLines())
46 node.Parent().ReplaceChild(node.Parent(), node, t)
47 return
48 }
49
50 node.SetLines(lines)
51 }
52
53 func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) {
54 block.SkipSpaces()
55 line, _ := block.PeekLine()
56 if line == nil {
57 return -1, -1
58 }
59 startLine, _ := block.Position()
60 width, pos := util.IndentWidth(line, 0)
61 if width > 3 {
62 return -1, -1
63 }
64 if width != 0 {
65 pos++
66 }
67 if line[pos] != '[' {
68 return -1, -1
69 }
70 block.Advance(pos + 1)
71 segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
72 if !found {
73 return -1, -1
74 }
75 var label []byte
76 if segments.Len() == 1 {
77 label = block.Value(segments.At(0))
78 } else {
79 for i := 0; i < segments.Len(); i++ {
80 s := segments.At(i)
81 label = append(label, block.Value(s)...)
82 }
83 }
84 if util.IsBlank(label) {
85 return -1, -1
86 }
87 if block.Peek() != ':' {
88 return -1, -1
89 }
90 block.Advance(1)
91 block.SkipSpaces()
92 destination, ok := parseLinkDestination(block)
93 if !ok {
94 return -1, -1
95 }
96 line, _ = block.PeekLine()
97 isNewLine := line == nil || util.IsBlank(line)
98
99 endLine, _ := block.Position()
100 _, spaces, _ := block.SkipSpaces()
101 opener := block.Peek()
102 if opener != '"' && opener != '\'' && opener != '(' {
103 if !isNewLine {
104 return -1, -1
105 }
106 ref := NewReference(label, destination, nil)
107 pc.AddReference(ref)
108 return startLine, endLine + 1
109 }
110 if spaces == 0 {
111 return -1, -1
112 }
113 block.Advance(1)
114 closer := opener
115 if opener == '(' {
116 closer = ')'
117 }
118 segments, found = block.FindClosure(opener, closer, linkFindClosureOptions)
119 if !found {
120 if !isNewLine {
121 return -1, -1
122 }
123 ref := NewReference(label, destination, nil)
124 pc.AddReference(ref)
125 block.AdvanceLine()
126 return startLine, endLine + 1
127 }
128 var title []byte
129 if segments.Len() == 1 {
130 title = block.Value(segments.At(0))
131 } else {
132 for i := 0; i < segments.Len(); i++ {
133 s := segments.At(i)
134 title = append(title, block.Value(s)...)
135 }
136 }
137
138 line, _ = block.PeekLine()
139 if line != nil && !util.IsBlank(line) {
140 if !isNewLine {
141 return -1, -1
142 }
143 ref := NewReference(label, destination, title)
144 pc.AddReference(ref)
145 return startLine, endLine
146 }
147
148 endLine, _ = block.Position()
149 ref := NewReference(label, destination, title)
150 pc.AddReference(ref)
151 return startLine, endLine + 1
152 }
153
View as plain text