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  }