66
|
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 }
|