github.com/crspeller/mattermost-server@v0.0.0-20190328001957-a200beb3d111/utils/markdown/markdown.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  // This package implements a parser for the subset of the CommonMark spec necessary for us to do
     5  // server-side processing. It is not a full implementation and lacks many features. But it is
     6  // complete enough to efficiently and accurately allow us to do what we need to like rewrite image
     7  // URLs for proxying.
     8  package markdown
     9  
    10  import (
    11  	"strings"
    12  )
    13  
    14  func isEscapable(c rune) bool {
    15  	return c > ' ' && (c < '0' || (c > '9' && (c < 'A' || (c > 'Z' && (c < 'a' || (c > 'z' && c <= '~'))))))
    16  }
    17  
    18  func isEscapableByte(c byte) bool {
    19  	return isEscapable(rune(c))
    20  }
    21  
    22  func isWhitespace(c rune) bool {
    23  	switch c {
    24  	case ' ', '\t', '\n', '\u000b', '\u000c', '\r':
    25  		return true
    26  	default:
    27  		return false
    28  	}
    29  }
    30  
    31  func isWhitespaceByte(c byte) bool {
    32  	return isWhitespace(rune(c))
    33  }
    34  
    35  func isNumeric(c rune) bool {
    36  	return c >= '0' && c <= '9'
    37  }
    38  
    39  func isNumericByte(c byte) bool {
    40  	return isNumeric(rune(c))
    41  }
    42  
    43  func isHex(c rune) bool {
    44  	return isNumeric(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
    45  }
    46  
    47  func isHexByte(c byte) bool {
    48  	return isHex(rune(c))
    49  }
    50  
    51  func isAlphanumeric(c rune) bool {
    52  	return isNumeric(c) || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
    53  }
    54  
    55  func isAlphanumericByte(c byte) bool {
    56  	return isAlphanumeric(rune(c))
    57  }
    58  
    59  func nextNonWhitespace(markdown string, position int) int {
    60  	for offset, c := range []byte(markdown[position:]) {
    61  		if !isWhitespaceByte(c) {
    62  			return position + offset
    63  		}
    64  	}
    65  	return len(markdown)
    66  }
    67  
    68  func nextLine(markdown string, position int) (linePosition int, skippedNonWhitespace bool) {
    69  	for i := position; i < len(markdown); i++ {
    70  		c := markdown[i]
    71  		if c == '\r' {
    72  			if i+1 < len(markdown) && markdown[i+1] == '\n' {
    73  				return i + 2, skippedNonWhitespace
    74  			}
    75  			return i + 1, skippedNonWhitespace
    76  		} else if c == '\n' {
    77  			return i + 1, skippedNonWhitespace
    78  		} else if !isWhitespaceByte(c) {
    79  			skippedNonWhitespace = true
    80  		}
    81  	}
    82  	return len(markdown), skippedNonWhitespace
    83  }
    84  
    85  func countIndentation(markdown string, r Range) (spaces, bytes int) {
    86  	for i := r.Position; i < r.End; i++ {
    87  		if markdown[i] == ' ' {
    88  			spaces++
    89  			bytes++
    90  		} else if markdown[i] == '\t' {
    91  			spaces += 4
    92  			bytes++
    93  		} else {
    94  			break
    95  		}
    96  	}
    97  	return
    98  }
    99  
   100  func trimLeftSpace(markdown string, r Range) Range {
   101  	s := markdown[r.Position:r.End]
   102  	trimmed := strings.TrimLeftFunc(s, isWhitespace)
   103  	return Range{r.Position, r.End - (len(s) - len(trimmed))}
   104  }
   105  
   106  func trimRightSpace(markdown string, r Range) Range {
   107  	s := markdown[r.Position:r.End]
   108  	trimmed := strings.TrimRightFunc(s, isWhitespace)
   109  	return Range{r.Position, r.End - (len(s) - len(trimmed))}
   110  }
   111  
   112  func relativeToAbsolutePosition(ranges []Range, position int) int {
   113  	rem := position
   114  	for _, r := range ranges {
   115  		l := r.End - r.Position
   116  		if rem < l {
   117  			return r.Position + rem
   118  		}
   119  		rem -= l
   120  	}
   121  	if len(ranges) == 0 {
   122  		return 0
   123  	}
   124  	return ranges[len(ranges)-1].End
   125  }
   126  
   127  func trimBytesFromRanges(ranges []Range, bytes int) (result []Range) {
   128  	rem := bytes
   129  	for _, r := range ranges {
   130  		if rem == 0 {
   131  			result = append(result, r)
   132  			continue
   133  		}
   134  		l := r.End - r.Position
   135  		if rem < l {
   136  			result = append(result, Range{r.Position + rem, r.End})
   137  			rem = 0
   138  			continue
   139  		}
   140  		rem -= l
   141  	}
   142  	return
   143  }
   144  
   145  func Parse(markdown string) (*Document, []*ReferenceDefinition) {
   146  	lines := ParseLines(markdown)
   147  	return ParseBlocks(markdown, lines)
   148  }