github.com/ashishbhate/mattermost-server@v5.11.1+incompatible/utils/markdown/blocks.go (about) 1 // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package markdown 5 6 import ( 7 "strings" 8 ) 9 10 type continuation struct { 11 Indentation int 12 Remaining Range 13 } 14 15 type Block interface { 16 Continuation(indentation int, r Range) *continuation 17 AddLine(indentation int, r Range) bool 18 Close() 19 AllowsBlockStarts() bool 20 HasTrailingBlankLine() bool 21 } 22 23 type blockBase struct{} 24 25 func (*blockBase) AddLine(indentation int, r Range) bool { return false } 26 func (*blockBase) Close() {} 27 func (*blockBase) AllowsBlockStarts() bool { return true } 28 func (*blockBase) HasTrailingBlankLine() bool { return false } 29 30 type ContainerBlock interface { 31 Block 32 AddChild(openBlocks []Block) []Block 33 } 34 35 type Range struct { 36 Position int 37 End int 38 } 39 40 func closeBlocks(blocks []Block, referenceDefinitions *[]*ReferenceDefinition) { 41 for _, block := range blocks { 42 block.Close() 43 if p, ok := block.(*Paragraph); ok && len(p.ReferenceDefinitions) > 0 { 44 *referenceDefinitions = append(*referenceDefinitions, p.ReferenceDefinitions...) 45 } 46 } 47 } 48 49 func ParseBlocks(markdown string, lines []Line) (*Document, []*ReferenceDefinition) { 50 document := &Document{} 51 var referenceDefinitions []*ReferenceDefinition 52 53 openBlocks := []Block{document} 54 55 for _, line := range lines { 56 r := line.Range 57 lastMatchIndex := 0 58 59 indentation, indentationBytes := countIndentation(markdown, r) 60 r = Range{r.Position + indentationBytes, r.End} 61 62 for i, block := range openBlocks { 63 if continuation := block.Continuation(indentation, r); continuation != nil { 64 indentation = continuation.Indentation 65 r = continuation.Remaining 66 additionalIndentation, additionalIndentationBytes := countIndentation(markdown, r) 67 r = Range{r.Position + additionalIndentationBytes, r.End} 68 indentation += additionalIndentation 69 lastMatchIndex = i 70 } else { 71 break 72 } 73 } 74 75 if openBlocks[lastMatchIndex].AllowsBlockStarts() { 76 if newBlocks := blockStart(markdown, indentation, r, openBlocks[:lastMatchIndex+1], openBlocks[lastMatchIndex+1:]); newBlocks != nil { 77 didAdd := false 78 for i := lastMatchIndex; i >= 0; i-- { 79 if container, ok := openBlocks[i].(ContainerBlock); ok { 80 if addedBlocks := container.AddChild(newBlocks); addedBlocks != nil { 81 closeBlocks(openBlocks[i+1:], &referenceDefinitions) 82 openBlocks = openBlocks[:i+1] 83 openBlocks = append(openBlocks, addedBlocks...) 84 didAdd = true 85 break 86 } 87 } 88 } 89 if didAdd { 90 continue 91 } 92 } 93 } 94 95 isBlank := strings.TrimSpace(markdown[r.Position:r.End]) == "" 96 if paragraph, ok := openBlocks[len(openBlocks)-1].(*Paragraph); ok && !isBlank { 97 paragraph.Text = append(paragraph.Text, r) 98 continue 99 } 100 101 closeBlocks(openBlocks[lastMatchIndex+1:], &referenceDefinitions) 102 openBlocks = openBlocks[:lastMatchIndex+1] 103 104 if openBlocks[lastMatchIndex].AddLine(indentation, r) { 105 continue 106 } 107 108 if paragraph := newParagraph(markdown, r); paragraph != nil { 109 for i := lastMatchIndex; i >= 0; i-- { 110 if container, ok := openBlocks[i].(ContainerBlock); ok { 111 if newBlocks := container.AddChild([]Block{paragraph}); newBlocks != nil { 112 closeBlocks(openBlocks[i+1:], &referenceDefinitions) 113 openBlocks = openBlocks[:i+1] 114 openBlocks = append(openBlocks, newBlocks...) 115 break 116 } 117 } 118 } 119 } 120 } 121 122 closeBlocks(openBlocks, &referenceDefinitions) 123 124 return document, referenceDefinitions 125 } 126 127 func blockStart(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block { 128 if r.Position >= r.End { 129 return nil 130 } 131 132 if start := blockQuoteStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { 133 return start 134 } else if start := listStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { 135 return start 136 } else if start := indentedCodeStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { 137 return start 138 } else if start := fencedCodeStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { 139 return start 140 } 141 142 return nil 143 } 144 145 func blockStartOrParagraph(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block { 146 if start := blockStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { 147 return start 148 } 149 if paragraph := newParagraph(markdown, r); paragraph != nil { 150 return []Block{paragraph} 151 } 152 return nil 153 }