link_ref.go (3383B)
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 // LinkReferenceParagraphTransformer is a ParagraphTransformer implementation 13 // that parses and extracts link reference from paragraphs. 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 }