package parser import ( "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" ) type linkReferenceParagraphTransformer struct { } // LinkReferenceParagraphTransformer is a ParagraphTransformer implementation // that parses and extracts link reference from paragraphs. var LinkReferenceParagraphTransformer = &linkReferenceParagraphTransformer{} func (p *linkReferenceParagraphTransformer) Transform(node *ast.Paragraph, reader text.Reader, pc Context) { lines := node.Lines() block := text.NewBlockReader(reader.Source(), lines) removes := [][2]int{} for { start, end := parseLinkReferenceDefinition(block, pc) if start > -1 { if start == end { end++ } removes = append(removes, [2]int{start, end}) continue } break } offset := 0 for _, remove := range removes { if lines.Len() == 0 { break } s := lines.Sliced(remove[1]-offset, lines.Len()) lines.SetSliced(0, remove[0]-offset) lines.AppendAll(s) offset = remove[1] } if lines.Len() == 0 { t := ast.NewTextBlock() t.SetBlankPreviousLines(node.HasBlankPreviousLines()) node.Parent().ReplaceChild(node.Parent(), node, t) return } node.SetLines(lines) } func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) { block.SkipSpaces() line, _ := block.PeekLine() if line == nil { return -1, -1 } startLine, _ := block.Position() width, pos := util.IndentWidth(line, 0) if width > 3 { return -1, -1 } if width != 0 { pos++ } if line[pos] != '[' { return -1, -1 } block.Advance(pos + 1) segments, found := block.FindClosure('[', ']', linkFindClosureOptions) if !found { return -1, -1 } var label []byte if segments.Len() == 1 { label = block.Value(segments.At(0)) } else { for i := 0; i < segments.Len(); i++ { s := segments.At(i) label = append(label, block.Value(s)...) } } if util.IsBlank(label) { return -1, -1 } if block.Peek() != ':' { return -1, -1 } block.Advance(1) block.SkipSpaces() destination, ok := parseLinkDestination(block) if !ok { return -1, -1 } line, _ = block.PeekLine() isNewLine := line == nil || util.IsBlank(line) endLine, _ := block.Position() _, spaces, _ := block.SkipSpaces() opener := block.Peek() if opener != '"' && opener != '\'' && opener != '(' { if !isNewLine { return -1, -1 } ref := NewReference(label, destination, nil) pc.AddReference(ref) return startLine, endLine + 1 } if spaces == 0 { return -1, -1 } block.Advance(1) closer := opener if opener == '(' { closer = ')' } segments, found = block.FindClosure(opener, closer, linkFindClosureOptions) if !found { if !isNewLine { return -1, -1 } ref := NewReference(label, destination, nil) pc.AddReference(ref) block.AdvanceLine() return startLine, endLine + 1 } var title []byte if segments.Len() == 1 { title = block.Value(segments.At(0)) } else { for i := 0; i < segments.Len(); i++ { s := segments.At(i) title = append(title, block.Value(s)...) } } line, _ = block.PeekLine() if line != nil && !util.IsBlank(line) { if !isNewLine { return -1, -1 } ref := NewReference(label, destination, title) pc.AddReference(ref) return startLine, endLine } endLine, _ = block.Position() ref := NewReference(label, destination, title) pc.AddReference(ref) return startLine, endLine + 1 }