github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/shared/markdown/markdown.go (about) 1 // Copyright (c) 2015-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 } 27 return false 28 } 29 30 func isWhitespaceByte(c byte) bool { 31 return isWhitespace(rune(c)) 32 } 33 34 func isNumeric(c rune) bool { 35 return c >= '0' && c <= '9' 36 } 37 38 func isNumericByte(c byte) bool { 39 return isNumeric(rune(c)) 40 } 41 42 func isHex(c rune) bool { 43 return isNumeric(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') 44 } 45 46 func isHexByte(c byte) bool { 47 return isHex(rune(c)) 48 } 49 50 func isAlphanumeric(c rune) bool { 51 return isNumeric(c) || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') 52 } 53 54 func isAlphanumericByte(c byte) bool { 55 return isAlphanumeric(rune(c)) 56 } 57 58 func nextNonWhitespace(markdown string, position int) int { 59 for offset, c := range []byte(markdown[position:]) { 60 if !isWhitespaceByte(c) { 61 return position + offset 62 } 63 } 64 return len(markdown) 65 } 66 67 func nextLine(markdown string, position int) (linePosition int, skippedNonWhitespace bool) { 68 for i := position; i < len(markdown); i++ { 69 c := markdown[i] 70 if c == '\r' { 71 if i+1 < len(markdown) && markdown[i+1] == '\n' { 72 return i + 2, skippedNonWhitespace 73 } 74 return i + 1, skippedNonWhitespace 75 } else if c == '\n' { 76 return i + 1, skippedNonWhitespace 77 } else if !isWhitespaceByte(c) { 78 skippedNonWhitespace = true 79 } 80 } 81 return len(markdown), skippedNonWhitespace 82 } 83 84 func countIndentation(markdown string, r Range) (spaces, bytes int) { 85 for i := r.Position; i < r.End; i++ { 86 if markdown[i] == ' ' { 87 spaces++ 88 bytes++ 89 } else if markdown[i] == '\t' { 90 spaces += 4 91 bytes++ 92 } else { 93 break 94 } 95 } 96 return 97 } 98 99 func trimLeftSpace(markdown string, r Range) Range { 100 s := markdown[r.Position:r.End] 101 trimmed := strings.TrimLeftFunc(s, isWhitespace) 102 return Range{r.Position, r.End - (len(s) - len(trimmed))} 103 } 104 105 func trimRightSpace(markdown string, r Range) Range { 106 s := markdown[r.Position:r.End] 107 trimmed := strings.TrimRightFunc(s, isWhitespace) 108 return Range{r.Position, r.End - (len(s) - len(trimmed))} 109 } 110 111 func relativeToAbsolutePosition(ranges []Range, position int) int { 112 rem := position 113 for _, r := range ranges { 114 l := r.End - r.Position 115 if rem < l { 116 return r.Position + rem 117 } 118 rem -= l 119 } 120 if len(ranges) == 0 { 121 return 0 122 } 123 return ranges[len(ranges)-1].End 124 } 125 126 func trimBytesFromRanges(ranges []Range, bytes int) (result []Range) { 127 rem := bytes 128 for _, r := range ranges { 129 if rem == 0 { 130 result = append(result, r) 131 continue 132 } 133 l := r.End - r.Position 134 if rem < l { 135 result = append(result, Range{r.Position + rem, r.End}) 136 rem = 0 137 continue 138 } 139 rem -= l 140 } 141 return 142 } 143 144 func Parse(markdown string) (*Document, []*ReferenceDefinition) { 145 lines := ParseLines(markdown) 146 return ParseBlocks(markdown, lines) 147 }