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 }