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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user