link_ref.go

  1package parser
  2
  3import (
  4	"github.com/yuin/goldmark/ast"
  5	"github.com/yuin/goldmark/text"
  6	"github.com/yuin/goldmark/util"
  7)
  8
  9type linkReferenceParagraphTransformer struct {
 10}
 11
 12// LinkReferenceParagraphTransformer is a ParagraphTransformer implementation
 13// that parses and extracts link reference from paragraphs.
 14var LinkReferenceParagraphTransformer = &linkReferenceParagraphTransformer{}
 15
 16func (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
 53func 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}