refactor: simplify stringx.Replacer, and avoid potential infinite loops (#2877)

* simplify replace

* backup

* refactor: simplify stringx.Replacer

* chore: add comments and const

* chore: add more tests

* chore: rename variable
This commit is contained in:
Kevin Wan
2023-02-14 10:18:02 +08:00
committed by GitHub
parent 9be17a2d28
commit cddf3875cf
4 changed files with 56 additions and 354 deletions

View File

@@ -1,9 +1,14 @@
package stringx
import (
"sort"
"strings"
)
// replace more than once to avoid overlapped keywords after replace.
// only try 2 times to avoid too many or infinite loops.
const replaceTimes = 2
type (
// Replacer interface wraps the Replace method.
Replacer interface {
@@ -32,29 +37,48 @@ func NewReplacer(mapping map[string]string) Replacer {
// Replace replaces text with given substitutes.
func (r *replacer) Replace(text string) string {
var buf strings.Builder
var paths []*node
target := []rune(text)
cur := r.node
for len(target) != 0 {
uselessLen, matchLen, nextPaths := cur.longestMatch(target, paths)
if uselessLen > 0 {
buf.WriteString(string(target[:uselessLen]))
target = target[uselessLen:]
}
if matchLen > 0 {
replaced := r.mapping[string(target[:matchLen])]
target = append([]rune(replaced), target[matchLen:]...)
}
if len(nextPaths) != 0 {
cur = nextPaths[len(nextPaths)-1]
paths = nextPaths
} else {
cur = r.node
paths = nil
for i := 0; i < replaceTimes; i++ {
var replaced bool
if text, replaced = r.doReplace(text); !replaced {
return text
}
}
return buf.String()
return text
}
func (r *replacer) doReplace(text string) (string, bool) {
chars := []rune(text)
scopes := r.find(chars)
if len(scopes) == 0 {
return text, false
}
sort.Slice(scopes, func(i, j int) bool {
if scopes[i].start < scopes[j].start {
return true
}
if scopes[i].start == scopes[j].start {
return scopes[i].stop > scopes[j].stop
}
return false
})
var buf strings.Builder
var index int
for i := 0; i < len(scopes); i++ {
scp := &scopes[i]
if scp.start < index {
continue
}
buf.WriteString(string(chars[index:scp.start]))
buf.WriteString(r.mapping[string(chars[scp.start:scp.stop])])
index = scp.stop
}
if index < len(chars) {
buf.WriteString(string(chars[index:]))
}
return buf.String(), true
}