github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/shared/markdown/blocks.go (about) 1 // Copyright (c) 2015-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) []*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 return referenceDefinitions 48 } 49 50 func ParseBlocks(markdown string, lines []Line) (*Document, []*ReferenceDefinition) { 51 document := &Document{} 52 var referenceDefinitions []*ReferenceDefinition 53 54 openBlocks := []Block{document} 55 56 for _, line := range lines { 57 r := line.Range 58 lastMatchIndex := 0 59 60 indentation, indentationBytes := countIndentation(markdown, r) 61 r = Range{r.Position + indentationBytes, r.End} 62 63 for i, block := range openBlocks { 64 if continuation := block.Continuation(indentation, r); continuation != nil { 65 indentation = continuation.Indentation 66 r = continuation.Remaining 67 additionalIndentation, additionalIndentationBytes := countIndentation(markdown, r) 68 r = Range{r.Position + additionalIndentationBytes, r.End} 69 indentation += additionalIndentation 70 lastMatchIndex = i 71 } else { 72 break 73 } 74 } 75 76 if openBlocks[lastMatchIndex].AllowsBlockStarts() { 77 if newBlocks := blockStart(markdown, indentation, r, openBlocks[:lastMatchIndex+1], openBlocks[lastMatchIndex+1:]); newBlocks != nil { 78 didAdd := false 79 for i := lastMatchIndex; i >= 0; i-- { 80 if container, ok := openBlocks[i].(ContainerBlock); ok { 81 if addedBlocks := container.AddChild(newBlocks); addedBlocks != nil { 82 referenceDefinitions = closeBlocks(openBlocks[i+1:], referenceDefinitions) 83 openBlocks = openBlocks[:i+1] 84 openBlocks = append(openBlocks, addedBlocks...) 85 didAdd = true 86 break 87 } 88 } 89 } 90 if didAdd { 91 continue 92 } 93 } 94 } 95 96 isBlank := strings.TrimSpace(markdown[r.Position:r.End]) == "" 97 if paragraph, ok := openBlocks[len(openBlocks)-1].(*Paragraph); ok && !isBlank { 98 paragraph.Text = append(paragraph.Text, r) 99 continue 100 } 101 102 referenceDefinitions = closeBlocks(openBlocks[lastMatchIndex+1:], referenceDefinitions) 103 openBlocks = openBlocks[:lastMatchIndex+1] 104 105 if openBlocks[lastMatchIndex].AddLine(indentation, r) { 106 continue 107 } 108 109 if paragraph := newParagraph(markdown, r); paragraph != nil { 110 for i := lastMatchIndex; i >= 0; i-- { 111 if container, ok := openBlocks[i].(ContainerBlock); ok { 112 if newBlocks := container.AddChild([]Block{paragraph}); newBlocks != nil { 113 referenceDefinitions = closeBlocks(openBlocks[i+1:], referenceDefinitions) 114 openBlocks = openBlocks[:i+1] 115 openBlocks = append(openBlocks, newBlocks...) 116 break 117 } 118 } 119 } 120 } 121 } 122 123 referenceDefinitions = closeBlocks(openBlocks, referenceDefinitions) 124 125 return document, referenceDefinitions 126 } 127 128 func blockStart(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block { 129 if r.Position >= r.End { 130 return nil 131 } 132 133 if start := blockQuoteStart(markdown, indentation, r); start != nil { 134 return start 135 } else if start := listStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { 136 return start 137 } else if start := indentedCodeStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { 138 return start 139 } else if start := fencedCodeStart(markdown, indentation, r); start != nil { 140 return start 141 } 142 143 return nil 144 } 145 146 func blockStartOrParagraph(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block { 147 if start := blockStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { 148 return start 149 } 150 if paragraph := newParagraph(markdown, r); paragraph != nil { 151 return []Block{paragraph} 152 } 153 return nil 154 }