replace.go

  1package regexp2
  2
  3import (
  4	"bytes"
  5	"errors"
  6
  7	"github.com/dlclark/regexp2/syntax"
  8)
  9
 10const (
 11	replaceSpecials     = 4
 12	replaceLeftPortion  = -1
 13	replaceRightPortion = -2
 14	replaceLastGroup    = -3
 15	replaceWholeString  = -4
 16)
 17
 18// MatchEvaluator is a function that takes a match and returns a replacement string to be used
 19type MatchEvaluator func(Match) string
 20
 21// Three very similar algorithms appear below: replace (pattern),
 22// replace (evaluator), and split.
 23
 24// Replace Replaces all occurrences of the regex in the string with the
 25// replacement pattern.
 26//
 27// Note that the special case of no matches is handled on its own:
 28// with no matches, the input string is returned unchanged.
 29// The right-to-left case is split out because StringBuilder
 30// doesn't handle right-to-left string building directly very well.
 31func replace(regex *Regexp, data *syntax.ReplacerData, evaluator MatchEvaluator, input string, startAt, count int) (string, error) {
 32	if count < -1 {
 33		return "", errors.New("Count too small")
 34	}
 35	if count == 0 {
 36		return "", nil
 37	}
 38
 39	m, err := regex.FindStringMatchStartingAt(input, startAt)
 40
 41	if err != nil {
 42		return "", err
 43	}
 44	if m == nil {
 45		return input, nil
 46	}
 47
 48	buf := &bytes.Buffer{}
 49	text := m.text
 50
 51	if !regex.RightToLeft() {
 52		prevat := 0
 53		for m != nil {
 54			if m.Index != prevat {
 55				buf.WriteString(string(text[prevat:m.Index]))
 56			}
 57			prevat = m.Index + m.Length
 58			if evaluator == nil {
 59				replacementImpl(data, buf, m)
 60			} else {
 61				buf.WriteString(evaluator(*m))
 62			}
 63
 64			count--
 65			if count == 0 {
 66				break
 67			}
 68			m, err = regex.FindNextMatch(m)
 69			if err != nil {
 70				return "", nil
 71			}
 72		}
 73
 74		if prevat < len(text) {
 75			buf.WriteString(string(text[prevat:]))
 76		}
 77	} else {
 78		prevat := len(text)
 79		var al []string
 80
 81		for m != nil {
 82			if m.Index+m.Length != prevat {
 83				al = append(al, string(text[m.Index+m.Length:prevat]))
 84			}
 85			prevat = m.Index
 86			if evaluator == nil {
 87				replacementImplRTL(data, &al, m)
 88			} else {
 89				al = append(al, evaluator(*m))
 90			}
 91
 92			count--
 93			if count == 0 {
 94				break
 95			}
 96			m, err = regex.FindNextMatch(m)
 97			if err != nil {
 98				return "", nil
 99			}
100		}
101
102		if prevat > 0 {
103			buf.WriteString(string(text[:prevat]))
104		}
105
106		for i := len(al) - 1; i >= 0; i-- {
107			buf.WriteString(al[i])
108		}
109	}
110
111	return buf.String(), nil
112}
113
114// Given a Match, emits into the StringBuilder the evaluated
115// substitution pattern.
116func replacementImpl(data *syntax.ReplacerData, buf *bytes.Buffer, m *Match) {
117	for _, r := range data.Rules {
118
119		if r >= 0 { // string lookup
120			buf.WriteString(data.Strings[r])
121		} else if r < -replaceSpecials { // group lookup
122			m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
123		} else {
124			switch -replaceSpecials - 1 - r { // special insertion patterns
125			case replaceLeftPortion:
126				for i := 0; i < m.Index; i++ {
127					buf.WriteRune(m.text[i])
128				}
129			case replaceRightPortion:
130				for i := m.Index + m.Length; i < len(m.text); i++ {
131					buf.WriteRune(m.text[i])
132				}
133			case replaceLastGroup:
134				m.groupValueAppendToBuf(m.GroupCount()-1, buf)
135			case replaceWholeString:
136				for i := 0; i < len(m.text); i++ {
137					buf.WriteRune(m.text[i])
138				}
139			}
140		}
141	}
142}
143
144func replacementImplRTL(data *syntax.ReplacerData, al *[]string, m *Match) {
145	l := *al
146	buf := &bytes.Buffer{}
147
148	for _, r := range data.Rules {
149		buf.Reset()
150		if r >= 0 { // string lookup
151			l = append(l, data.Strings[r])
152		} else if r < -replaceSpecials { // group lookup
153			m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
154			l = append(l, buf.String())
155		} else {
156			switch -replaceSpecials - 1 - r { // special insertion patterns
157			case replaceLeftPortion:
158				for i := 0; i < m.Index; i++ {
159					buf.WriteRune(m.text[i])
160				}
161			case replaceRightPortion:
162				for i := m.Index + m.Length; i < len(m.text); i++ {
163					buf.WriteRune(m.text[i])
164				}
165			case replaceLastGroup:
166				m.groupValueAppendToBuf(m.GroupCount()-1, buf)
167			case replaceWholeString:
168				for i := 0; i < len(m.text); i++ {
169					buf.WriteRune(m.text[i])
170				}
171			}
172			l = append(l, buf.String())
173		}
174	}
175
176	*al = l
177}