Files
Atay-Makhzan/modules/markup/markdown/math/inline_parser.go
T

176 lines
4.4 KiB
Go
Raw Normal View History

2022-09-13 17:33:37 +01:00
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2022-09-13 17:33:37 +01:00
package math
import (
"bytes"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
)
type inlineParser struct {
2024-12-14 13:43:05 +08:00
trigger []byte
endBytesSingleDollar []byte
endBytesDoubleDollar []byte
2025-04-05 11:56:48 +08:00
endBytesParentheses []byte
enableInlineDollar bool
2022-09-13 17:33:37 +01:00
}
2025-04-05 11:56:48 +08:00
func NewInlineDollarParser(enableInlineDollar bool) parser.InlineParser {
return &inlineParser{
trigger: []byte{'$'},
endBytesSingleDollar: []byte{'$'},
endBytesDoubleDollar: []byte{'$', '$'},
enableInlineDollar: enableInlineDollar,
}
2022-09-13 17:33:37 +01:00
}
2025-04-05 11:56:48 +08:00
var defaultInlineParenthesesParser = &inlineParser{
trigger: []byte{'\\', '('},
endBytesParentheses: []byte{'\\', ')'},
2022-09-13 17:33:37 +01:00
}
2025-04-05 11:56:48 +08:00
func NewInlineParenthesesParser() parser.InlineParser {
return defaultInlineParenthesesParser
2022-09-13 17:33:37 +01:00
}
2022-10-05 19:55:36 +01:00
// Trigger triggers this parser on $ or \
2022-09-13 17:33:37 +01:00
func (parser *inlineParser) Trigger() []byte {
2024-12-14 13:43:05 +08:00
return parser.trigger
2022-09-13 17:33:37 +01:00
}
func isPunctuation(b byte) bool {
return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':'
}
2025-04-05 11:56:48 +08:00
func isParenthesesClose(b byte) bool {
return b == ')'
}
2022-09-13 17:33:37 +01:00
func isAlphanumeric(b byte) bool {
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
2022-09-13 17:33:37 +01:00
}
2025-12-10 23:49:24 +08:00
func isInMarkdownLinkText(block text.Reader, lineAfter []byte) bool {
return block.PrecendingCharacter() == '[' && bytes.HasPrefix(lineAfter, []byte("]("))
}
2022-09-13 17:33:37 +01:00
// Parse parses the current line and returns a result of parsing.
func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, _ := block.PeekLine()
2022-10-05 19:55:36 +01:00
2024-12-14 13:43:05 +08:00
if !bytes.HasPrefix(line, parser.trigger) {
2022-10-05 19:55:36 +01:00
// We'll catch this one on the next time round
2022-09-13 17:33:37 +01:00
return nil
}
2024-12-14 13:43:05 +08:00
var startMarkLen int
var stopMark []byte
checkSurrounding := true
if line[0] == '$' {
startMarkLen = 1
stopMark = parser.endBytesSingleDollar
if len(line) > 1 {
2025-03-29 22:32:28 +01:00
switch line[1] {
case '$':
2024-12-14 13:43:05 +08:00
startMarkLen = 2
stopMark = parser.endBytesDoubleDollar
2025-03-29 22:32:28 +01:00
case '`':
2024-12-14 13:43:05 +08:00
pos := 1
for ; pos < len(line) && line[pos] == '`'; pos++ {
}
startMarkLen = pos
stopMark = bytes.Repeat([]byte{'`'}, pos)
stopMark[len(stopMark)-1] = '$'
checkSurrounding = false
}
}
} else {
startMarkLen = 2
2025-04-05 11:56:48 +08:00
stopMark = parser.endBytesParentheses
}
if line[0] == '$' && !parser.enableInlineDollar && (len(line) == 1 || line[1] != '`') {
return nil
2024-12-14 13:43:05 +08:00
}
if checkSurrounding {
precedingCharacter := block.PrecendingCharacter()
if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
// need to exclude things like `a$` from being considered a start
return nil
}
2022-09-13 17:33:37 +01:00
}
2022-10-05 19:55:36 +01:00
// move the opener marker point at the start of the text
2024-12-14 13:43:05 +08:00
opener := startMarkLen
2022-10-05 19:55:36 +01:00
// Now look for an ending line
2024-12-06 20:00:24 +08:00
depth := 0
2024-12-06 12:29:09 +08:00
ender := -1
for i := opener; i < len(line); i++ {
2024-12-14 13:43:05 +08:00
if depth == 0 && bytes.HasPrefix(line[i:], stopMark) {
2024-12-06 12:29:09 +08:00
succeedingCharacter := byte(0)
2024-12-14 13:43:05 +08:00
if i+len(stopMark) < len(line) {
succeedingCharacter = line[i+len(stopMark)]
2024-12-06 12:29:09 +08:00
}
// check valid ending character
2025-04-05 11:56:48 +08:00
isValidEndingChar := isPunctuation(succeedingCharacter) || isParenthesesClose(succeedingCharacter) ||
2025-12-10 23:49:24 +08:00
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0 ||
succeedingCharacter == '$' ||
isInMarkdownLinkText(block, line[i+len(stopMark):])
2024-12-14 13:43:05 +08:00
if checkSurrounding && !isValidEndingChar {
2024-12-06 12:29:09 +08:00
break
}
ender = i
2022-10-05 19:55:36 +01:00
break
}
2024-12-06 12:29:09 +08:00
if line[i] == '\\' {
i++
continue
2022-10-05 19:55:36 +01:00
}
2025-03-29 22:32:28 +01:00
switch line[i] {
case '{':
2024-12-06 20:00:24 +08:00
depth++
2025-03-29 22:32:28 +01:00
case '}':
2024-12-06 20:00:24 +08:00
depth--
}
2024-12-06 12:29:09 +08:00
}
if ender == -1 {
return nil
2022-09-13 17:33:37 +01:00
}
block.Advance(opener)
_, pos := block.Position()
2024-12-14 13:43:05 +08:00
node := NewInline()
2022-10-05 19:55:36 +01:00
segment := pos.WithStop(pos.Start + ender - opener)
2022-09-13 17:33:37 +01:00
node.AppendChild(node, ast.NewRawTextSegment(segment))
2024-12-14 13:43:05 +08:00
block.Advance(ender - opener + len(stopMark))
trimBlock(node, block)
2022-09-13 17:33:37 +01:00
return node
}
func trimBlock(node *Inline, block text.Reader) {
if node.IsBlank(block.Source()) {
return
}
// trim first space and last space
first := node.FirstChild().(*ast.Text)
if !(!first.Segment.IsEmpty() && block.Source()[first.Segment.Start] == ' ') {
return
}
last := node.LastChild().(*ast.Text)
if !(!last.Segment.IsEmpty() && block.Source()[last.Segment.Stop-1] == ' ') {
return
}
first.Segment = first.Segment.WithStart(first.Segment.Start + 1)
last.Segment = last.Segment.WithStop(last.Segment.Stop - 1)
}