2020-08-09 22:29:54 +00:00
|
|
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
|
|
// See LICENSE.txt for license information.
|
2018-11-18 17:55:05 +00:00
|
|
|
|
|
|
|
package markdown
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type continuation struct {
|
|
|
|
Indentation int
|
|
|
|
Remaining Range
|
|
|
|
}
|
|
|
|
|
|
|
|
type Block interface {
|
|
|
|
Continuation(indentation int, r Range) *continuation
|
|
|
|
AddLine(indentation int, r Range) bool
|
|
|
|
Close()
|
|
|
|
AllowsBlockStarts() bool
|
|
|
|
HasTrailingBlankLine() bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type blockBase struct{}
|
|
|
|
|
|
|
|
func (*blockBase) AddLine(indentation int, r Range) bool { return false }
|
|
|
|
func (*blockBase) Close() {}
|
|
|
|
func (*blockBase) AllowsBlockStarts() bool { return true }
|
|
|
|
func (*blockBase) HasTrailingBlankLine() bool { return false }
|
|
|
|
|
|
|
|
type ContainerBlock interface {
|
|
|
|
Block
|
|
|
|
AddChild(openBlocks []Block) []Block
|
|
|
|
}
|
|
|
|
|
|
|
|
type Range struct {
|
|
|
|
Position int
|
|
|
|
End int
|
|
|
|
}
|
|
|
|
|
2020-12-31 13:48:12 +00:00
|
|
|
func closeBlocks(blocks []Block, referenceDefinitions []*ReferenceDefinition) []*ReferenceDefinition {
|
2018-11-18 17:55:05 +00:00
|
|
|
for _, block := range blocks {
|
|
|
|
block.Close()
|
|
|
|
if p, ok := block.(*Paragraph); ok && len(p.ReferenceDefinitions) > 0 {
|
2020-12-31 13:48:12 +00:00
|
|
|
referenceDefinitions = append(referenceDefinitions, p.ReferenceDefinitions...)
|
2018-11-18 17:55:05 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-31 13:48:12 +00:00
|
|
|
return referenceDefinitions
|
2018-11-18 17:55:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func ParseBlocks(markdown string, lines []Line) (*Document, []*ReferenceDefinition) {
|
|
|
|
document := &Document{}
|
|
|
|
var referenceDefinitions []*ReferenceDefinition
|
|
|
|
|
|
|
|
openBlocks := []Block{document}
|
|
|
|
|
|
|
|
for _, line := range lines {
|
|
|
|
r := line.Range
|
|
|
|
lastMatchIndex := 0
|
|
|
|
|
|
|
|
indentation, indentationBytes := countIndentation(markdown, r)
|
|
|
|
r = Range{r.Position + indentationBytes, r.End}
|
|
|
|
|
|
|
|
for i, block := range openBlocks {
|
|
|
|
if continuation := block.Continuation(indentation, r); continuation != nil {
|
|
|
|
indentation = continuation.Indentation
|
|
|
|
r = continuation.Remaining
|
|
|
|
additionalIndentation, additionalIndentationBytes := countIndentation(markdown, r)
|
|
|
|
r = Range{r.Position + additionalIndentationBytes, r.End}
|
|
|
|
indentation += additionalIndentation
|
|
|
|
lastMatchIndex = i
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if openBlocks[lastMatchIndex].AllowsBlockStarts() {
|
|
|
|
if newBlocks := blockStart(markdown, indentation, r, openBlocks[:lastMatchIndex+1], openBlocks[lastMatchIndex+1:]); newBlocks != nil {
|
|
|
|
didAdd := false
|
|
|
|
for i := lastMatchIndex; i >= 0; i-- {
|
|
|
|
if container, ok := openBlocks[i].(ContainerBlock); ok {
|
2020-08-09 22:29:54 +00:00
|
|
|
if addedBlocks := container.AddChild(newBlocks); addedBlocks != nil {
|
2020-12-31 13:48:12 +00:00
|
|
|
referenceDefinitions = closeBlocks(openBlocks[i+1:], referenceDefinitions)
|
2018-11-18 17:55:05 +00:00
|
|
|
openBlocks = openBlocks[:i+1]
|
2020-08-09 22:29:54 +00:00
|
|
|
openBlocks = append(openBlocks, addedBlocks...)
|
2018-11-18 17:55:05 +00:00
|
|
|
didAdd = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if didAdd {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
isBlank := strings.TrimSpace(markdown[r.Position:r.End]) == ""
|
|
|
|
if paragraph, ok := openBlocks[len(openBlocks)-1].(*Paragraph); ok && !isBlank {
|
|
|
|
paragraph.Text = append(paragraph.Text, r)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-12-31 13:48:12 +00:00
|
|
|
referenceDefinitions = closeBlocks(openBlocks[lastMatchIndex+1:], referenceDefinitions)
|
2018-11-18 17:55:05 +00:00
|
|
|
openBlocks = openBlocks[:lastMatchIndex+1]
|
|
|
|
|
|
|
|
if openBlocks[lastMatchIndex].AddLine(indentation, r) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if paragraph := newParagraph(markdown, r); paragraph != nil {
|
|
|
|
for i := lastMatchIndex; i >= 0; i-- {
|
|
|
|
if container, ok := openBlocks[i].(ContainerBlock); ok {
|
|
|
|
if newBlocks := container.AddChild([]Block{paragraph}); newBlocks != nil {
|
2020-12-31 13:48:12 +00:00
|
|
|
referenceDefinitions = closeBlocks(openBlocks[i+1:], referenceDefinitions)
|
2018-11-18 17:55:05 +00:00
|
|
|
openBlocks = openBlocks[:i+1]
|
|
|
|
openBlocks = append(openBlocks, newBlocks...)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-31 13:48:12 +00:00
|
|
|
referenceDefinitions = closeBlocks(openBlocks, referenceDefinitions)
|
2018-11-18 17:55:05 +00:00
|
|
|
|
|
|
|
return document, referenceDefinitions
|
|
|
|
}
|
|
|
|
|
|
|
|
func blockStart(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block {
|
|
|
|
if r.Position >= r.End {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-16 22:47:22 +00:00
|
|
|
if start := blockQuoteStart(markdown, indentation, r); start != nil {
|
2018-11-18 17:55:05 +00:00
|
|
|
return start
|
|
|
|
} else if start := listStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil {
|
|
|
|
return start
|
|
|
|
} else if start := indentedCodeStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil {
|
|
|
|
return start
|
2021-10-16 22:47:22 +00:00
|
|
|
} else if start := fencedCodeStart(markdown, indentation, r); start != nil {
|
2018-11-18 17:55:05 +00:00
|
|
|
return start
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func blockStartOrParagraph(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block {
|
|
|
|
if start := blockStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil {
|
|
|
|
return start
|
|
|
|
}
|
|
|
|
if paragraph := newParagraph(markdown, r); paragraph != nil {
|
|
|
|
return []Block{paragraph}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|