// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package markdown import ( "unicode/utf8" ) func parseLinkDestination(markdown string, position int) (raw Range, next int, ok bool) { if position >= len(markdown) { return } if markdown[position] == '<' { isEscaped := false for offset, c := range []byte(markdown[position+1:]) { if isEscaped { isEscaped = false if isEscapableByte(c) { continue } } if c == '\\' { isEscaped = true } else if c == '<' { break } else if c == '>' { return Range{position + 1, position + 1 + offset}, position + 1 + offset + 1, true } else if isWhitespaceByte(c) { break } } } openCount := 0 isEscaped := false for offset, c := range []byte(markdown[position:]) { if isEscaped { isEscaped = false if isEscapableByte(c) { continue } } switch c { case '\\': isEscaped = true case '(': openCount++ case ')': if openCount < 1 { return Range{position, position + offset}, position + offset, true } openCount-- default: if isWhitespaceByte(c) { return Range{position, position + offset}, position + offset, true } } } return Range{position, len(markdown)}, len(markdown), true } func parseLinkTitle(markdown string, position int) (raw Range, next int, ok bool) { if position >= len(markdown) { return } originalPosition := position var closer byte switch markdown[position] { case '"', '\'': closer = markdown[position] case '(': closer = ')' default: return } position++ for position < len(markdown) { switch markdown[position] { case '\\': position++ if position < len(markdown) && isEscapableByte(markdown[position]) { position++ } case closer: return Range{originalPosition + 1, position}, position + 1, true default: position++ } } return } func parseLinkLabel(markdown string, position int) (raw Range, next int, ok bool) { if position >= len(markdown) || markdown[position] != '[' { return } originalPosition := position position++ for position < len(markdown) { switch markdown[position] { case '\\': position++ if position < len(markdown) && isEscapableByte(markdown[position]) { position++ } case '[': return case ']': if position-originalPosition >= 1000 && utf8.RuneCountInString(markdown[originalPosition:position]) >= 1000 { return } return Range{originalPosition + 1, position}, position + 1, true default: position++ } } return } // As a non-standard feature, we allow image links to specify dimensions of the image by adding "=WIDTHxHEIGHT" // after the image destination but before the image title like ![alt](http://example.com/image.png =100x200 "title"). // Both width and height are optional, but at least one of them must be specified. func parseImageDimensions(markdown string, position int) (raw Range, next int, ok bool) { if position >= len(markdown) { return } originalPosition := position // Read = position += 1 if position >= len(markdown) { return } // Read width hasWidth := false for isNumericByte(markdown[position]) { hasWidth = true position += 1 } // Look for early end of dimensions if isWhitespaceByte(markdown[position]) || markdown[position] == ')' { return Range{originalPosition, position - 1}, position, true } // Read the x if markdown[position] != 'x' && markdown[position] != 'X' { return } position += 1 // Read height hasHeight := false for isNumericByte(markdown[position]) { hasHeight = true position += 1 } // Make sure the there's no trailing characters if !isWhitespaceByte(markdown[position]) && markdown[position] != ')' { return } if !hasWidth && !hasHeight { // At least one of width or height is required return } return Range{originalPosition, position - 1}, position, true }