You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

163 lines
3.5 KiB

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