forked from gitea/gitea
178 lines
4.0 KiB
Go
178 lines
4.0 KiB
Go
|
package regexp2
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
|
||
|
"github.com/dlclark/regexp2/syntax"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
replaceSpecials = 4
|
||
|
replaceLeftPortion = -1
|
||
|
replaceRightPortion = -2
|
||
|
replaceLastGroup = -3
|
||
|
replaceWholeString = -4
|
||
|
)
|
||
|
|
||
|
// MatchEvaluator is a function that takes a match and returns a replacement string to be used
|
||
|
type MatchEvaluator func(Match) string
|
||
|
|
||
|
// Three very similar algorithms appear below: replace (pattern),
|
||
|
// replace (evaluator), and split.
|
||
|
|
||
|
// Replace Replaces all occurrences of the regex in the string with the
|
||
|
// replacement pattern.
|
||
|
//
|
||
|
// Note that the special case of no matches is handled on its own:
|
||
|
// with no matches, the input string is returned unchanged.
|
||
|
// The right-to-left case is split out because StringBuilder
|
||
|
// doesn't handle right-to-left string building directly very well.
|
||
|
func replace(regex *Regexp, data *syntax.ReplacerData, evaluator MatchEvaluator, input string, startAt, count int) (string, error) {
|
||
|
if count < -1 {
|
||
|
return "", errors.New("Count too small")
|
||
|
}
|
||
|
if count == 0 {
|
||
|
return "", nil
|
||
|
}
|
||
|
|
||
|
m, err := regex.FindStringMatchStartingAt(input, startAt)
|
||
|
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
if m == nil {
|
||
|
return input, nil
|
||
|
}
|
||
|
|
||
|
buf := &bytes.Buffer{}
|
||
|
text := m.text
|
||
|
|
||
|
if !regex.RightToLeft() {
|
||
|
prevat := 0
|
||
|
for m != nil {
|
||
|
if m.Index != prevat {
|
||
|
buf.WriteString(string(text[prevat:m.Index]))
|
||
|
}
|
||
|
prevat = m.Index + m.Length
|
||
|
if evaluator == nil {
|
||
|
replacementImpl(data, buf, m)
|
||
|
} else {
|
||
|
buf.WriteString(evaluator(*m))
|
||
|
}
|
||
|
|
||
|
count--
|
||
|
if count == 0 {
|
||
|
break
|
||
|
}
|
||
|
m, err = regex.FindNextMatch(m)
|
||
|
if err != nil {
|
||
|
return "", nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if prevat < len(text) {
|
||
|
buf.WriteString(string(text[prevat:]))
|
||
|
}
|
||
|
} else {
|
||
|
prevat := len(text)
|
||
|
var al []string
|
||
|
|
||
|
for m != nil {
|
||
|
if m.Index+m.Length != prevat {
|
||
|
al = append(al, string(text[m.Index+m.Length:prevat]))
|
||
|
}
|
||
|
prevat = m.Index
|
||
|
if evaluator == nil {
|
||
|
replacementImplRTL(data, &al, m)
|
||
|
} else {
|
||
|
al = append(al, evaluator(*m))
|
||
|
}
|
||
|
|
||
|
count--
|
||
|
if count == 0 {
|
||
|
break
|
||
|
}
|
||
|
m, err = regex.FindNextMatch(m)
|
||
|
if err != nil {
|
||
|
return "", nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if prevat > 0 {
|
||
|
buf.WriteString(string(text[:prevat]))
|
||
|
}
|
||
|
|
||
|
for i := len(al) - 1; i >= 0; i-- {
|
||
|
buf.WriteString(al[i])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return buf.String(), nil
|
||
|
}
|
||
|
|
||
|
// Given a Match, emits into the StringBuilder the evaluated
|
||
|
// substitution pattern.
|
||
|
func replacementImpl(data *syntax.ReplacerData, buf *bytes.Buffer, m *Match) {
|
||
|
for _, r := range data.Rules {
|
||
|
|
||
|
if r >= 0 { // string lookup
|
||
|
buf.WriteString(data.Strings[r])
|
||
|
} else if r < -replaceSpecials { // group lookup
|
||
|
m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
|
||
|
} else {
|
||
|
switch -replaceSpecials - 1 - r { // special insertion patterns
|
||
|
case replaceLeftPortion:
|
||
|
for i := 0; i < m.Index; i++ {
|
||
|
buf.WriteRune(m.text[i])
|
||
|
}
|
||
|
case replaceRightPortion:
|
||
|
for i := m.Index + m.Length; i < len(m.text); i++ {
|
||
|
buf.WriteRune(m.text[i])
|
||
|
}
|
||
|
case replaceLastGroup:
|
||
|
m.groupValueAppendToBuf(m.GroupCount()-1, buf)
|
||
|
case replaceWholeString:
|
||
|
for i := 0; i < len(m.text); i++ {
|
||
|
buf.WriteRune(m.text[i])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func replacementImplRTL(data *syntax.ReplacerData, al *[]string, m *Match) {
|
||
|
l := *al
|
||
|
buf := &bytes.Buffer{}
|
||
|
|
||
|
for _, r := range data.Rules {
|
||
|
buf.Reset()
|
||
|
if r >= 0 { // string lookup
|
||
|
l = append(l, data.Strings[r])
|
||
|
} else if r < -replaceSpecials { // group lookup
|
||
|
m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
|
||
|
l = append(l, buf.String())
|
||
|
} else {
|
||
|
switch -replaceSpecials - 1 - r { // special insertion patterns
|
||
|
case replaceLeftPortion:
|
||
|
for i := 0; i < m.Index; i++ {
|
||
|
buf.WriteRune(m.text[i])
|
||
|
}
|
||
|
case replaceRightPortion:
|
||
|
for i := m.Index + m.Length; i < len(m.text); i++ {
|
||
|
buf.WriteRune(m.text[i])
|
||
|
}
|
||
|
case replaceLastGroup:
|
||
|
m.groupValueAppendToBuf(m.GroupCount()-1, buf)
|
||
|
case replaceWholeString:
|
||
|
for i := 0; i < len(m.text); i++ {
|
||
|
buf.WriteRune(m.text[i])
|
||
|
}
|
||
|
}
|
||
|
l = append(l, buf.String())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*al = l
|
||
|
}
|