1 | package regexp2
|
---|
2 |
|
---|
3 | import (
|
---|
4 | "bytes"
|
---|
5 | "errors"
|
---|
6 |
|
---|
7 | "github.com/dlclark/regexp2/syntax"
|
---|
8 | )
|
---|
9 |
|
---|
10 | const (
|
---|
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
|
---|
19 | type 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.
|
---|
31 | func 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.
|
---|
116 | func 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 |
|
---|
144 | func 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 | }
|
---|