summaryrefslogtreecommitdiffstats
path: root/utils/markdown/blocks.go
diff options
context:
space:
mode:
Diffstat (limited to 'utils/markdown/blocks.go')
-rw-r--r--utils/markdown/blocks.go153
1 files changed, 153 insertions, 0 deletions
diff --git a/utils/markdown/blocks.go b/utils/markdown/blocks.go
new file mode 100644
index 000000000..14972f943
--- /dev/null
+++ b/utils/markdown/blocks.go
@@ -0,0 +1,153 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+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
+}
+
+func closeBlocks(blocks []Block, referenceDefinitions *[]*ReferenceDefinition) {
+ for _, block := range blocks {
+ block.Close()
+ if p, ok := block.(*Paragraph); ok && len(p.ReferenceDefinitions) > 0 {
+ *referenceDefinitions = append(*referenceDefinitions, p.ReferenceDefinitions...)
+ }
+ }
+}
+
+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 {
+ if newBlocks := container.AddChild(newBlocks); newBlocks != nil {
+ closeBlocks(openBlocks[i+1:], &referenceDefinitions)
+ openBlocks = openBlocks[:i+1]
+ openBlocks = append(openBlocks, newBlocks...)
+ 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
+ }
+
+ closeBlocks(openBlocks[lastMatchIndex+1:], &referenceDefinitions)
+ 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 {
+ closeBlocks(openBlocks[i+1:], &referenceDefinitions)
+ openBlocks = openBlocks[:i+1]
+ openBlocks = append(openBlocks, newBlocks...)
+ break
+ }
+ }
+ }
+ }
+ }
+
+ closeBlocks(openBlocks, &referenceDefinitions)
+
+ return document, referenceDefinitions
+}
+
+func blockStart(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block {
+ if r.Position >= r.End {
+ return nil
+ }
+
+ if start := blockQuoteStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil {
+ 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
+ } else if start := fencedCodeStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil {
+ 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
+}